SE-0217 - The "Unwrap or Die" operator

I'm all for giving help. What I'm not for is spending an operator and standard library surface area on a use-case that I consider of dubious value in general and can be expressed with more general means whose implementation would benefit the language overall. Once written, the ?? fatalError() idiom is unmistakably clear and readable. I consider !! to be less readable and not significantly more discoverable. There's no great win here, and adding stuff to the language/standard library should meet a higher bar, IMO.

27 Likes

I've been a touch hesitant to support !! String, but I would strongly support !! Never.

let x = value !! fatalError("Something bad happened!") is less terse, but it opens the door to better logging / customization by the consumer.

I also couldn't agree more with Dave's take on avoiding ?? Never.

If ! is the unsafe companion of ?, then !! should be the unsafe companion of ??.

3 Likes

While I agree, to play devil's advocate here: new users could legitimately misinterpret the idiom to mean that some sort of error is thrown and would only halt execution if it isn't caught elsewhere (given that it uses the word "error" and ?? might look like the inverse operation of try?).

1 Like

Whether to rename fatalError is a separate discussion (Error was introduced after fatalError, FWIW).

4 Likes

For you, IMO.

On the other side, the mental model described by @davedelong here holds straight just as well, and is quite sensible. The (? / !) pair as (don't crash / crash) has much more precedent than ?? Never: try? / try!, as? / as!, etc? / etc!


Edit: besides, if !! is sugar for ?? fatalError(...), everything is there right in place! You'll use ?? fatalError(...), maybe enjoy a little sip of !! sometimes, and everybody will be happy.

3 Likes

See also:

As I have demonstrated several times now, it is a misunderstanding that supporting x ?? fatalError() violates that precedent in any way. Making the change proposed would only validate and reinforce that misunderstanding. I'm strongly against making changes to the language or library whose justification is based on misunderstanding.

7 Likes

OK you win. Good night folks!

And as I explained, that position is pedantically correct but practically wrong.

But if there’s no room for flexibility in understanding here, then I’d rather see this proposal withdrawn than implemented in what I’d consider to be a fundamentally bad way.

Like @gwendal.roue, I’m bowing out of this discussion. I don’t have the mental or emotional energy to keep fighting this level of pedantry.

2 Likes

The goal of a review discussion shouldn't be to "win" but to gather information relevant to evaluating the proposal. @dabrahams, I think you've stated your case, and likewise, @davedelong and @gwendal.roue's objections are equally clear. To keep the discussion productive, I propose we move on.

12 Likes

I wasn't going to add a "me too," but since we've obviously reached a point where it wasn't crystal clear to everybody else on the list: me too.

1 Like

One last thought while on my iPhone:

@cal makes a great point about !! () -> Never providing a customization point.

I would be happy to see that in an accepted solution as long as there was also the shorter !! String version.

And if the “Namespacing common error scenarios” proposal is accepted, I would also expect a !! FatalReason to be part of that.

3 Likes

The use case for this feature is basically "I want to force-unwrap a value, but I am a mere mortal who is sometimes confused about my preconditions". In other words, the use case for this feature is basically "I want to force-unwrap a value".

But let's focus on one specific sub-case: users who don't really understand optionals yet. I find this Stack Overflow question instructive on the question of whether developers are currently understanding force-unwraps. This error message only comes up when you either force-unwrap incorrectly, or when you access an IUO when you shouldn't, and a developer who understands these features should not need to look up the message. Thus, traffic on this question may be a good proxy for confusion about force-unwrapping and/or IUOs.

As I write this, the question has 216,821 views, 281 upvotes (meaning users thought the question itself was a good one), and 134 favorites (meaning users bookmarked it for later reference). The top answer has 497 upvotes and has been opened to community editing so it can serve as an authoritative source. These are all huge numbers, akin to, for instance, the question about EXC_BAD_ACCESS in Objective-C. Taken together, these numbers suggest to me that users frequently force-unwrap inappropriately and struggle to understand what went wrong.

Moreover, they continue to struggle. I went back and looked at this question in the Wayback Machine so I could chart its activity over time:

Unwrap%20stack%20overflow

It doesn't look like people are getting much less confused about when to force unwrap.

To me, the crucial part of this proposal is that it modifies the fix-it on optional misuse, replacing the "Insert '!'" fix-it with a pair which insert either ?? or !!. I think this would help users who don't understand optionals yet in three ways:

  1. It would offer an option besides force unwrapping, so users would be aware that there were other ways to fix the issue.
  2. It would replace the force-unwrap fix-it with one which requires more thought to use (you have to fill in the placeholder for the right-hand side), so you're less tempted to "just make it work" by choosing that option.
  3. The placeholder's wording implies that you should only use !! when you're certain the value can't be nil, so some users may figure out that it's not the construct they need.

Without !!, we could still change the fix-it to suggest ?? or ! as alternatives. But inserting ! would still be the easier fix, and there would still be nothing to make it obvious that this might not be the right thing to do.

Maybe this isn't the right solution. But it's hard for me to see this use-case as having "dubious value" when 5,700 people have struggled with the status quo in the last three weeks.

(P.S. It looks like @jtbandes has been moderating this Stack Overflow question and making sure it remains helpful for confused Swift users. Thank you!)

9 Likes

I certainly agree that what you highlight is a problem in the learning and teaching of Swift. However, I see the proposal as setting us back rather than moving us forward in this respect:

Consider how long that (very nice!) answer is on StackOverflow. I suspect that providing yet another option--essentially, another flavor of force unwrapping--is only going to help new users become more confused about the feature. And I'd imagine that it would be rather a setback for those intermediate users who have already heard the advice "don't force unwrap," now to be told that there's a brand new one (can I use this one because it's different from the other one? or should I also avoid it because it's a force unwrap?).

8 Likes

As I'm trying to form an opinion and write some form of review, I read the replies and find myself constantly swayed on the various sides. It seems to me there are good reasons to go in every direction. Here's my train of thoughts:

  • I like the idea of !! "explanation" because it encourages the use of meaningful error messages. But if it does not support telling you which file and line the error comes from, then it's a regression from plain ! in my opinion.

  • I like the idea of having !! fatalError("message") as !! makes it clearer that we're stopping as opposed to ?? fatalError("message"). But the later ought to be allowed anyway if Never becomes a bottom type. Also, forcing Never does not really guaranty the program will stop (someone could throw an exception, run an infinite loop, etc.). So for these two reasons !! fatalError("message") seems problematic to me.

  • I think !! doesn't work very well with optional chaining:

    var result = someObject!.run()
    // vs.
    var result = (someObject !! "did forget to load your nib file?").run()
    

    This could make a fixit suggestion unwelcome if it becomes the default.

  • It's hard to gauge how much introducing a new unwrap syntax would help beginners. I do agree with the argument that it might have the opposite effect and make things more confusing since now you have two syntaxes for roughly the same thing.

  • If beginners have difficulty understanding what's happening with failed unwraps, I think the the solution should be to improve the error message instead inventing a new syntax. For instance:

    fatal error: force-unwrap of optional value with '!' failed: 'myArray.first' is nil

    I think that would be more effective than asking users to write an explanation to fill the placeholder of a fix-it.

I think in the end I'm unconvinced that !! "message" is worth including.

11 Likes

Here is the thing where I personally disagree with your post. For me the postfix ? is "execute the expression if possible or fallback to a no-op for the further chain", while the postfix ! is "execute the expression with the assumption it's not nil, if the assumption is wrong than the crash is your fault". In that sense !! does not make sense at all since it signals that it will do a force-unwrap on the lhs rather then unwrapping it safely before crashing on rhs. That said the unwrap or die operator would look like this ?! which I already mentioned in the post #49.

3 Likes

-1

If users don't understand optionals let's make them more understandable: a well-informed workaround is still a workaround.

A think I already stated my concerns at the time: I personally consider force-unwrapping always harmful, but a necessary evil in some cases (when dealing with XIBs, for example), and I never use ! in my code if not for things like URL initialization with a string (and even there, I try to cook up a type that represents an URL in a type-safe way). I also never use preconditions, which are very popular in dynamic languages: Swift type system is powerful enough to always avoid them, or almost always. At the time, my position encountered a lot of resistance, that I didn't expect, because from the very beginning I consider the strict safety of Swift as a clear advantage, that should be worked around only in extreme, exceptional cases. Somehow many people didn't feel the same, and I honestly still don't understand the arguments in favor of !.

I think a path forward for Swift should enforce the basic design principles ("Opting for safety sometimes means Swift will feel strict, but we believe that clarity saves time in the long run") instead of relaxing them. But I understand the argument that !! will make the user more concerned with their force unwrapping, resulting in (possibly) more thought put into the matter: but to really have this effect, we should consequently remove the ! postfix, as! and try!, that would be substituted, respectively, by !! _, _ as? _ !! _ and try? _ !! _. This way users would always be forced to better document their forced unwrapping, resulting in a net positive.

2 Likes

One of the most important aspects of this proposal is its value as developer documentation, as lightweight literate programming. There’s been a lot of discussion of syntactic and type system questions (which are interesting & important), but let’s not lose sight of the effect on code readability.

The heart of the proposal is to capture a valuable piece of information: the thinking underlying a force unwrap. That piece of information will be seen far more often in the code than at runtime; in fact, it is precisely developers seeing and understanding it in the code that will prevent it from being seen at runtime!

I’d thus like to draw attention a little more to how different approaches work as information design in the code. In this example code from upthread, there is an assumption that the compiler cannot enforce, but the developer has helpfully chosen to communicate. What is that assumption? How easy is it for another developer to spot it and understand it in each of the following syntaxes?

return request(method,
    data: urlEncodedParams.data(using: String.Encoding.ascii)! // A URL-escaped string is already ASCII
    contentType: "application/x-www-form-urlencoded",
    requestMutation: requestMutation)

return request(method,
    data: urlEncodedParams.data(using: String.Encoding.ascii) !! "A URL-escaped string is already ASCII",
    contentType: "application/x-www-form-urlencoded",
    requestMutation: requestMutation)

return request(method,
    data: urlEncodedParams.data(using: String.Encoding.ascii) ?? fatalError("Assumption failed: A URL-escaped string is already ASCII"),
    contentType: "application/x-www-form-urlencoded",
    requestMutation: requestMutation)

The last has the advantage that it makes the logic and control flow more explicit, but has the disadvantage that it moves the documentation 31 characters farther away from where it is relevant. I’d argue that the documentation, not the control flow, is the crucial information here.

Maybe one of these hypothetical namespaced errors could split the difference here by moving some of the contextualizing info (“assumption failed”) from the message text to the process-halting function’s name? Just making something up:

return request(method,
    data: urlEncodedParams.data(using: String.Encoding.ascii) ?? Fatal.assumption("A URL-escaped string is already ASCII"),
    contentType: "application/x-www-form-urlencoded",
    requestMutation: requestMutation)

That’s not ideal either; I’m just throwing it out there for consideration.

I’m not deeply wedded to the specific syntax of !! "explanation", but would be disappointed if the ultimate solution represented a readability regression from // explanation.

7 Likes

+1

I write the guard with fatalError more than I might like to admit. It is the correct strategy in many situations.

I've read the proposal and the discussion.

1 Like

The standard library is meant to support the language's built-in and near-built-in features. So it should approach "simple as it can be but not simpler," to paraphrase (disputed) Einstein. This borderline feature is supposed to be part of a fix-it, so it could be categorized to be part of the standard library.

Terms of Service

Privacy Policy

Cookie Policy