SE-0217 - The "Unwrap or Die" operator

A strong -1.

No.

To elaborate:

Examples of Real-World Use

None of these use cases are able to convince me:

let event = NSApp.currentEvent!

This code clearly states that there should be an event. If the unwrap does fail, it's clearly because there's no event (for some unknown reason).

let event = NSApp.currentEvent !! "Trying to get current event for right click, but there's no event"

How is this any better? Only the obvious is stated here, but there is no clue as to the reason why there's no event.

let existing = childViewControllers as! Array<TableRowViewController>

This code clearly states that the childViewControllers should be TableRowViewControllers.

let existing = childViewControllers as? Array<TableRowViewController> !! "TableViewController must only have TableRowViewControllers as children"

Again, this only states the obvious, as do all the other examples in the proposal.

IMHO, the use of !! here offers no additional value. In fact, I would actively remove it from my projects.

Encourages a thoughtful approach to unwrapping,

I don't agree here, for two reasons:

  1. Force unwrap should only be used when an optional can not or should not be nil. It should be obvious from the immediately surrounding code that this is the case. If it isn't obvious, then there's a high risk of shooting yourself in the foot and you're better off with a proper guard than trying to explain to yourself (or others) why it shouldn't be nil.
  2. Just like force unwrap is misused by lazy (or beginning) programmers, so too will !! become a lazy (wo)man's form of error handling. I feel an explicit guard encourages more thought than !!, as it offers options. Perhaps the error isn't really an error after all and you can just log a warning and return? Perhaps the error is recoverable, should be handled by the caller and you can use throw instead? When all else fails, there's still fatalError.

Provides a succinct and easily-taught form for new Swift learners.

As someone whose job it is to teach new learners, I can say with lots of confidence that this won't be the case. As explained above, new learners will misuse !! as a way to quickly print an error message, ignoring proper (thoughtful) error handling all together.

No. As explained above, I don't feel like this proposal warrants a new operator and even risks achieving the opposite of what it's trying to do.

I've read the proposal and reviewed some of my projects to look for areas where this operator could add value. I found none.

17 Likes

While I admire the creativity of this proposal, I don’t think it is the right direction for Swift. Yes, writing

    let value = wrappedValue !! “message”

is shorter than the equivalent

    guard let value = wrappedValue else {
        fatalError(“message”)
    }

but it is also more cryptic. The “guard” version says clearly what is happening and it is easier to modify if you later decide that a fatalError is not what you want. Some people will never use this operator and it will be an impediment to comprehension when they encounter it in others’ code.

7 Likes

I'm not quite sure if it worth to add as a syntax in swift, first, is that the syntax !! is conflicting with double unwrapping, but a more important reason is that the desired behavior is entirely achievable within the current Swift syntax, what you need is simply a global function, which myself used a lot (see below).

TL;DR: I simply don't think it's necessary to mess up with the syntax when the desired effect can be archive with the current swift syntax.

func Fatal<T>(_ err: String) -> T { fatalError(err) }

usage:

let x = something ?? Fatal("some errorMessage here")

or even

let x = someCondition ? value : Fatal("some errorMessage here")
3 Likes
  • What is your evaluation of the proposal?

-1. IMO the motivation is not strong enough to justify addition of this operator in the stdlib.

  • Is the problem being addressed significant enough to warrant a change to Swift?

No, I don't think so. I doubt having a shorthand operator makes much sense in this case. I am sure it will be useful sometimes but we can always use guard, which is not a lot of extra code.

  • Does this proposal fit well with the feel and direction of Swift?

I think this proposal violates the fundamental API goal "Clarity is more important than brevity". The shorthand is kind of confusing because I will need to remember which method is called by this operator (precondition/fatalError).

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Read the proposal.

5 Likes

I thought about this too with something like panic() but this requires more logging of where the line crash happened (and others like calling func etc). otherwise the crashing line logged will always show up as where fatalError was called which wont be much help or be a sutable replacement for fatalError.

1 Like

Without giving an opinion on the inclusion of the proposal. I will say if this were to be included it should incorporate the call context future direction as part of the proposal. Because if the motivation of this proposal is to improve debugging not having context would be a regression over a trap and a comment. This would be especially true in a non-IDE environment where the program output is not sufficient and debugging tools are not easily accessible.

Introducing the Never-rhs variant of !! allows you to:

  • Retain unsafe semantics with !! over ??. Limiting the rhs to Never ensures that any non-nil value will always have a non-returning outcome.
  • Preserve the context information without extra compiler work (#file, #function, #line)
  • Allow the rhs use of the universal-error scenarios discussed in this thread and proposal: Introducing Namespacing for Common Swift Error Scenarios - #22 by davedelong, also allowing the rhs extension to instances of the FatalReason, which has been designed for expansion to specific use scenarios:
wrappedURL !! fatalError(reason: .unconstructableURL)
1 Like

I'm not trying to push my reasoning against the inclusion of the !! operator, everything I had to say about it has been said already by others and myself in this thread. There is still one thing that makes me wonder.

Why is the new operator does not look like this ?! ?

When I created the custom unwrap or trap operator (two years ago) I used ?! since it signals that the lhs is an optional while the rhs is Never. The ?? operator has already two overloads where rhs is non-optional or optional. The proposed !! operator does not force unwrap the lhs in that same same like ?? safely unwraps lhs. Operator symmetry does not add any symantical information but rather creates a new source of confusion.


The only use-case I had for this operator was to provide a little tedious to generate but rather safely looking API when working with interface builder outlets.

1 Like

Probably -0.5. I was initially pro the idea, but after considering, I think there is value in the idea that a force unwrap should generally be self-documenting already. Should more documentation be required, an in-line comment documenting why the assumption should be safe would probably be more appropriate than a word or two stating the reason which may not give much value.

On first glance, yes. But after consideration, I’m not sure it adds much value above the standard detail.

If this added value, I’d say it’s the right operator and would fit. All the standard reasoning is valid except I don’t see it actually adding anything to everyday code, and the exception cases can be worked around with fatalError where the description is more in-depth

N/A

Quick reading of the thread.

3 Likes

If Never becomes the bottom type then ?? will as a side effect it will allow x ?? fatalError(“...”). Therefore probably better to allow ?? to have a () -> Never rhs now and !! as a shortcut to x ?? fatalError(“...”).

My feeling is that we should embrace a possible future Swift rather than fight it. Also it will give some useful experience of a bottom type.

PS This contradicts my earlier feeling that I didn’t care if it was spelled ?? or !!.

IMHO, this is a strawman. Of course these kinds of messages are not helpful. But I believe the purpose of a fatalError should not be describing the obvious, but provide additional reasoning for the assumption; i.e. much like good comments, it should describe the why not the what. E.g. let event = NSApp.currentEvent !! "current event should have been set in initializer" (or whatever).

I'll give a more specific example: I could build a struct like this:

struct Sum {
    let operands: [Int]
    init(_ first: Int, _ second: Int, _ rest: [Int]) {
       self.operands = [first, second] + rest
    }
}

My code doesn't allow me to initialise this struct with fewer than two operands. However, my compiler doesn't know this, so if I want to access the first operand of a sum I have to catch this, e.g. like this:

guard let firstOperand = mySum.operands.first else {
    fatalError("sum should have at least 2 operands")
}

which IMHO is much better diagnostics than just "mySum.operands.first was force unwrapped but was nil" (or whatever the exact output is).

1 Like

If the examples were written like these instead:

let event = NSApp.currentEvent
    !! "rightClick action should only be triggered by an event"
let existing = childViewControllers as? [TableRowViewController]
    !! "nib should contain only TableRow children"

Would you feel differently? Because if so, your objection is to the proposal, not to the feature. The fact that you can write a vacuous message string on an assertion doesn't—and shouldn't—stop us from putting message strings on assertions.

(I also notice you didn't quote the examples where a value was interpolated into the message string, which are a bit stronger than these.)

1 Like

When this was discussed previously, I thought it was the wrong solution to the right problem. I'm much more interested in a deeper solution to the problem of programmers letting optionals propagate in contexts where it would be more appropriate to handle them when they first appear.

While this proposal does address that in part, I don't think it digs deep enough into the problem. However, digging deeper is a different discussion, so I don't want to head in that direction here.

Part of me wants to reject the !! operator because it's really, really insubstantial. Functionally, it's identical to the ! operator, just with a different message. In that sense, it doesn't fix anything.

However, I think I'm going to give it a +1 after all, if somewhat hesitantly. That's because I've seen a lot of developer questions in the last few months where their app has crashed with the standard message ("Fatal error: Unexpectedly found nil while unwrapping an Optional value").

I've noticed that many developers have no idea what to do with this. They understand that their app will crash if "Swift tries to unwrap" a nil, but because it's a canned message, they don't quite understand that they did it. They tend to think it was something that was done to them.

For that reason, and that reason alone, I suspect that it will help if the error message is one that the developer themself wrote. It changes the scenario from "it crashed" to "I crashed it", and I think that will help relatively inexperienced developers (inexperienced in Swift, I mean) grasp that the correct action is in their own hands.

I'm not yet convinced about the revised fixit wording. I want to think about it a bit longer, while this debate (I'm sure) rages on for a while.

2 Likes

Is it really that much better to warrant:

  1. An additional operator in the language.
  2. The use of !! sum should have at least 2 operands every single time you read from operands using first, last, ... ?

I say no. If operands.first! crashes than that is obviously because it was nil, but shouldn't be. The error message "sum should have at least 2 operands" is of no help here, as it only states what you already know: that there should be an operand. It doesn't offer any hints as to the cause of the problem. In general, I don't think there are many cases where you could add a useful message. The only times when you can point at the cause of the problem in the error message is when the cause it obvious. But if it's obvious, then you don't need an error message ...

1 Like

No :slight_smile: As explained above, I feel ! should only be used when it is clear that a value is not nil. If it takes more than a few seconds for you to prove that, then the case is probably too complex for ! and should use more explicit error handling.

To give an example: The project I'm working on is currently at about 7.5K lines. There are 124 occurrences of ! in this project. Of these 124 occurrences, most are related to initial setup and parsing static values (often in unit tests). I found only one that isn't 100% obvious. Here it is (somewhat simplified):

guard form.isValid else {
 // amazing error handling
}
let date = form.date!

if date! fails to unwrap, I'm pretty sure you can guess where the bug is, even without a comment or !!?

I'm impartial to this. the ?? + Never variant is good enough for me too.

If this access happens more often, this can really be encapsulated in a method on Sum, so this isn't a counter-argument IMHO.

I do believe that the alternative error message is more helpful. This might not even be my code. Some other team member might have written this assertion. Later on, I might change the Sum initialiser so it allows for zero operands (for whatever reason). If I at some point get a crash with the specific error message, I know exactly what I did wrong. The "was nil but shouldn't be" could leave me scratching my head (as in "why did someone assume first could be unwrapped"?).

And this was only an example off the top of my head. More importantly, the unwrapping could be happening much further away from the introduction of the nil, then just looking at the force unwrap wouldn't be very helpful.

+1 on the proposal but I'm not sure about the syntax...

let thing = someThing !! "someThing should never be nil because..."

...because the diagnostic message looks like a value when in actual fact it's a statement, one that will crash your program if executed, and could be misleading especially given it's superficial similarity to ||, ternaries and the other existing coalescing/chaining operators.

3 Likes

I shared my thoughts on the proposal in its earlier stages; the proposal remains substantially the same, and my thoughts therefore haven't changed much either. There's little that hasn't already been brought up again in the thread, but I'll review the proposal as succinctly as I can:


I am quite opposed to the feature as proposed.

In earlier discussions, we discussed the idea of () -> Never being permitted on the right-hand side of ?? (which would be consistent with Never eventually being a true bottom type) with some amount of community approval, and I'm glad to see that it's brought up again here.

But this proposal rejects that well-received idea. What it proposes in its stead, a new operator spelled !!, has (in my opinion) several significant defects:

First, as an operator, it adds to the burden of syntax that Swift users have to learn. In fact, since the intention is for this operator to become a commonly used facility, it would increase that burden for new users. A common critique of Swift, and not an undeserved one, is that it has a lot of syntax, and this feature would add to rather than subtract from that problem if adopted.

Coming from other languages, working with optionals can be pretty hard--adding more to the options for dealing with them makes the language less approachable rather than more. New users already have to contend with the question of when to use force-unwrapping rather than if let or optional chaining; with this feature, they'd also be faced with the question of when to use ! rather than !!. The proposal envisions this as a matter of house style (more on that below), meaning that there's no one good answer for a new user.

Second, there is no reason why someone who wishes to use this feature could not trivially write an extension for themselves; in fact, as the proposal mentions, people have done exactly that with satisfactory results. The proposal itself envisions !! as a "positive house standards choice". Such choices related to house style, particularly when trivially implementable, more appropriately belong to the set of features that are implemented by the parties making that choice rather than those vended to all users of Swift.

Third, it makes implicit what would be explicit in expr ?? fatalError("Reason"): namely, that what occurs in the case of a nil value is a runtime error. As others have pointed out, !! typographically resembles ||, and the right-hand side looks like a plain string with no indication that there's a fatal error involved. After all, one critique of ! is that it is too succinct a way to spell an operation that can trap; !! would displace such uses without addressing that critique.

Fourth, the examples in the proposal text are demonstrably not useful and mostly entirely redundant, as @svanimpe points out. I brought up this critique during earlier phases of the proposal, and still the problem persists. Therefore, I cannot agree with @beccadax that the objection "is to the proposal, not to the feature."

If, after months, the many authors of the proposal don't/can't/won't write a meaningful right-hand side to illustrate the proper use of the feature, how do we anticipate users to do so in their code? This is a problem intrinsic to the proposed feature. I'm not at all placated by the fact that @beccadax might be able to come up with one or two good examples. What the situation suggests to me is that the most common real-world uses will look like what is shown in the proposal: users will be told not to use ! and they will instead pervasively decorate their code with error messages that recite what ! means. In the aggregate, it will not improve Swift code in the wild; it will merely make Swift code more verbose to read.

It appears that the proposal was not initially motivated by a problem at all, but by a desire to incorporate a particular syntax. Indeed, that's how the entire proposal is written: "Motivation: 'Unwrap or Die' has been widely adopted by the community." The reasons seem to have been included only to justify that choice after it was made. For reasons I give above, I do not believe that the operator presented here encourages more "thoughtful" coding than the alternative ?? fatalError() syntax, and I see no evidence that it promotes any useful reasons to be given for runtime traps.

I feel the proposal does not fit well with the feel and direction of Swift.

I'm not aware of similar features elsewhere, although evidently others have made this a common extension.

I participated in many phases of this proposal and have given this topic some in-depth thought.

18 Likes

This is a fairly insulting and judgmental comment. Please reconsider before posting such comments again.

This proposal hasn't been updated in months, because, frankly, none of us work for Apple. The amount of time we have sunk in to this proposal easily runs in to the tens of thousands of dollars, if we were to have billed for it.

Additionally, we were all surprised when the proposal came up for review; none of us had any indication or warning or notice that such a thing was going to or did happen; we discovered it accidentally. If we had known, perhaps we might have taken the time to update it.

In the future, do not assume that everyone has the same luxury of time to pour in to the forums and evolution process that you do.

4 Likes

Sorry--I meant no offense by it. Quite the opposite: I fully believe that the you and co-authors have spent lots of time and care in the proposal, as much as possible. And yet, the problem persists. Therefore, it must not be a problem with lack of effort or a reflection on the quality of your work on the proposal; instead, it's a point against the idea being proposed and intrinsic to the feature.

6 Likes