SE-0217 - The "Unwrap or Die" operator

What if there is more than one trapping-fail on the line? Would there be an alternate "/*! ... */" syntax to intersperse after each fail point?

2 Likes

-1.

Most of examples come from iOS/MacOS development where programmers work with code written in Objective-C, e.g. with UIKit framework when you build table view.

Swift is designed to be nil-safe language, and introducing such operator as part of standard library is opposite to its ideas.

On my opinion, Swift should encourage to do not fail with force unwrap, especially newcomers. Instead, language should encourage to re-write such code: remove optional, provide default value, etc. If no and we 100% percents sure that we want to "unwrap or die" we can use simple guard statement or write operator/function just for our needs.

Another thing is that this code not explicit to anyone. For newcomers it will "just fail you app somehow", for experienced users it will be another thing to keep in mind - what exactly will my program do when I use this operator when with methods like fatalError I can read documentation right inside IDE.

If the main idea to make optionals more clear for newcomers, this is not the solution. It just adds more sugar and magic to the optionals, not an explanation.

No, I haven't.

I read the final proposal and all messages in the topic.

2 Likes

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

What I would like to see in the future of the ! unwrap operator is compile-time checking. The compiler would try to verify that any value being force-unwrapped is not nil (using logic similar to how it verifies that all variables are initialized at time of their use).

The compiler would give a warning if it could not directly see that it is okay to unwrap the value. To make the warning go away you would have to either:

(1) Rearrange the logic of your program. For example, place an if-statement around the affected code, or add an appropriate guard statement.

(2) Insert a precondition before the applicable line of code. You would basically be saying, “I know it is not immediately obvious that this value is not nil but please take my word for it, compiler.” Then if the program crashes, it will crash on the precondition, not on the force-unwrap. That’s where it should crash because that’s where the error is — it is in your false assumption that the unwrapped value is not nil.

No more trembling every time you type !. With compile-time checking, you can use ! as freely as any other operator. If you make a logic error, the compiler will tell you.

if a == nil || a! == 0 { return }    // Correct
if a == nil && a! == 0 { return }    // “Cannot verify ‘a’ is not nil.”

This is more the direction I would like to see Swift go in.

1 Like

-1

I've wanted to support this proposal, but, in the end, it simply doesn't seem to fit Swift.

The syntax of Optional<T> !! String is cryptic, and IMO would be a step backwards; a step towards C, and away from clarity of intent. Having an operator stand between, and handle, two different types at the same time would lead to more confusion for new users IMO.

In Swift, we should say what we mean. In that direction lay formulations of the same idea, but which express the user's intent with clarity. I do not champion it as an addition to the Standard Library, but a set of methods on Optional, like the following, would be much more in line with Swift:
.unwrappedButIfNilUse(defaultValue: T)
.unwrappedButIfNilStop(because: String)
.unwrappedButIfNilCall(function: ()->Never)
.unwrappedAndWillNotBeNil(because: String)

A thank you is owed to the sponsors of this proposal. One and all, they are inspiring.

I've studied the proposal, followed the pitch, and have reviewed this thread in detail.

2 Likes

I'm not against having /*! ... */ syntax for completeness, but you could also just split the statement across multiple lines with a //! comment for each (or just decide to have a single comment apply to both traps).

I have already given a detailed response to this proposal here: Resolved: Insert "!" is a bad fixit so I’ll try not to repeat myself too much here other than to say I am strongly -1 on this.

To me the actual problem being addressed here is legitimate: that force unwrapping and optionals generally are difficult to understand for a beginner.

The solution proposed here doesn’t seem to be adding anything of value though: every !! “Explanation text” in the proposal has been a case of documenting “what”, rather than “why”. I understand this as a “commenting poor practice” 101. In every case it’s obvious that e.g. “such and such was not a subclass of x”, but how does that help a new user of the codebase at hand, or a new user of Swift?

The issue with force unwrapping is that it should really only be used when you don’t know why the unwrap failed. For everything else you should probably be writing something more explicit (with a guard else fatalError or similar).

The fact that force unwrapping is suggested as a/the fixit is the real issue here. Obviously it shouldn’t be, provided you can give a reasonable explanation why the value may be nil (in which case there are alternatives: ??, and guard else).

Optionals are confusing and we need better diagnostics and more reasonable fixits, but adding yet another operator to the mix is not the best solution to this, IMO it is not even a “good” one.

10 Likes

I want to add a few words about the genesis of this proposal. I have contributed only so much as trying to coordinate a lot of people all of whom are passionate about including !! in the language as an alternative to ??. This proposal includes feedback and direction from more members of the Swift development community with no evolution involvement than any other I've worked on. The people who wanted this feature were using this feature in a variety of forms across many projects.

The proposal grew from developers who wanted sugar for the following:

guard let x = x else {
    fatalError("Reason why x can't be nil")
}

Any focus on learners and new developers is an attempt to appease additional voices during the design phase, which honestly grew completely out of control.

I'm surprised how many people voted in the following fashion: "I am completely -1 on this proposal but I wouldn't mind one of the Never versions." Let me suggest that if you want sugar for this specific construct that you are in fact "+1" for the proposal, even if you don't like the primary design.

A vote against this proposal says: "I do not think Swift should expand its operator set or expand the meaning of the existing ?? operator to include sugar for this construct."

This is, as others have pointed out, and exhausting and overwrought train wreck of a proposal and a proposal review.

I hope as you give consideration to this in the end that you remember, it's just "sugar yes" or "sugar no". The form and design of that sugar can be resolved once that question is answered.

7 Likes

I strongly disagree with this characterization. SE–217 is a specific proposal which delineates a concrete design for an !! operator.

Weighing in for or against SE–217 is exactly and entirely an expression of opinion about the particular design laid out in the proposal. It does not say anything about one’s views regarding other hypothetical future proposals.

17 Likes

I would like to weigh in by reminding that it's not just the +1 or -1 that is needed for a review, but also the gathering of consensus about it; that's why there's a list of questions for people to answer about their motivation. And it may very well be that the proposal is asked to undergo another round of changes, in collaboration with the authors, if we find that the consensus is mixed or uncovers use cases or concrete concerns.

This is a creative and collaborative process between us all. We have different roles and different needs, but I believe it is important to remark that this should be the spirit in which this is taken. I would personally ask that this be kept into account, and not erase neither concern nor, importantly, need, even when it may feel contentious.

12 Likes

I’ll add a strong second to what @millenomi wrote. Swift Evolution would be both a more pleasant forum and a more successful one if we all kept in mind that ultimately the core team decides, and our job is to support their decision-making. That means in part providing a not just a poll but a map of community support for an idea: +1, -1, “yes to the spirit but not this specific design,” etc. It also means providing perspectives, examples, questions, and alternatives the core team may not have considered.

It doesn’t mean winning arguments with each other.

At best, it would mean achieving consensus among all of us reviewers about what options the core team could consider, and what the tradeoffs and implications of those options would be.

At the very least, it should mean a respectful acknowledgement that opinions and arguments will all be heard by the core team, and having stated them clearly and critiqued them thoughtfully is sufficient.

This is something both moderators and participants (myself included) could be better about. When a back-and-forth stops adding new information, it is no longer useful; when it becomes hostile, it is counterproductive. (I appreciated @Joe_Groff’s gentle nipping in the bud such a back-and-forth upthread. I know I’ve been guilty in the past, and I hope the moderator gives me the same respectful nudge next time I am.) I wish these discussions were more creative and collaborative, as Lily said, and less adversarial.

As for this particular proposal, it seems like there’s a lot of interest in several of its goals, and a lot of contention about this particular solution. I trust the core team to hear the many ideas here — even the ones less vehemently stated! — and bring their good judgement to choosing where to take this next. I’m glad that a small group is making that decision, even if I disagree with it, because the alternative is design by mob committee — and that won’t lead to the language any of us want. This is ultimately a cooperative game we’re playing: we all win or lose together as the language we all share evolves.

9 Likes

I have been too busy lately to participate more in the forums, but in light of the last post from @Erica_Sadun and the others following it, here is my evaluation:

Negative. While I applaud the intention behind the proposal, I don't think the proposal is the correct way to deal with the issues raised in its motivation section.

I believe the key issues in this design space that actually need addressing are:

  • How to help developers better understand and correctly use force unwrap
  • How to make the compiler fix-its better and improve runtime diagnostic when the supposedly impossible (force unwrap of nil) happens.

I think we can do better addressing both of the above, without !! operator.

I don't think sugaring if let ... else { fatalError(...) } is a worthy goal to justify a brand new operator.

In contrast, addition of ?? was well worth it, because it is really useful in so many situations. It is now hard to imagine programming without it. On the other hand, if we run into if let ... else { fatalError(...) } frequently enough to justify !! sugar, we better go back and rethink our designs.

Addressing the two issues I mentioned above, yes. Sugaring if let ... else { fatalError(...) }: No.

No, as indicated above.

n/a

Read the proposal and read about 50% of the posts around this issue in multiple threads.

1 Like

Evaluation: This proposal seeks to address a common pitfall when using Optionals in Swift by introducing a syntactic sugar for a best practice: documenting the precondition when force-unwrapping the optional. The benefits of the proposed !! <# Document the force-unwrapping precondition #> syntax include:

  • Encourages thoughtful application of force-unwrapping, compared to mindlessly slapping ! after optional value to make code compile. Replacing most uses of ! use with !! would be, in my estimation, a clear benefit.
  • Additionally it turns that documentation into useful error message, when the precondition fails at runtime. Given this is a programmer error, it’s a bonus that it can also include additional information to help in debugging thanks to the lazily evaluated string interpolation.
  • The assertion documenting string prepended with !! nicely stacks on a new line when composing multiline expressions (e.g. method argument chains), without detracting too much from the main purpose of the code. It fits better than inline comments. This is the mayor usability win over the underlying multiline expression which it sugars.

Perhaps calling this an unwrap precondition operator would be pedagogicaly beneficial over the “unwrap or die operator” (it doesn’t play well in the context of official documentation).

Concrete implementation details should be left for later code review (!! being alias for ?? -> Never makes perfect sense; including correct line numbers in error message is a must; preconditionFailure should be the correct sugared function invocation pending SR-905 fix). Making Swift Evolution Proposal Review into 500 person code review is counterproductive waste of time that fosters unnecessarily hostile atmosphere.

Problem significance: As demonstrated with StackOverflow question popularity above, the “insert !” fixit is making this problem as prevalent as NullPointer Exceptions in Java. So even though Swift avoids the billion dollar mistake by disallowing null references and has a type-safe remedy in form of Optional, we are not reaping its full benefits in practice.

Wider programming perspective: From what I’ve seen in practical use of Haskell’s Maybe and Scala’s Option, they don’t suffer from the issue. The problem this proposal seeks to address is unique to Swift because of our use of force-unwrapped operator.

Swift fit: Proposal logically extends the family of Swift’s optional handling operators with new signpost: !! clearly indicates dangerous operation and is easy to teach, in the spirit of Swift’s progressive disclosure for new learners. It offer’s a better alternative to !, which should be reserved as absolute power tool: to be used only when it’s crystal clear it cannot trap. Then we can address the root cause of the problem: replace compiler’s ! fixit with ?? and !! (in that order).

8 Likes

One last comment about how this operator came to exist in my codebase, and why I believe it deserves inclusion into the standard library.

When I'm writing code, I generally want Swift to stay out of my way. Dealing with sharp corners in Swift is disruptive to the creative process.

This operator stemmed from the observation that it's common to want to unwrap optionals, but that the existing patterns all are either too verbose or fail the "is this expressive enough" test.

if let

if let foo = maybeFoo {
    // use foo here
}

This is the standard approach, but if you know ahead of time that maybeFoo can be known to have a value, this introduces unnecessary indentation and cyclomatic complexity.

Postfix-!

let foo = maybeFoo!

This approach definitely wins on terseness, but it fails on expressivity. I have no explanation for why maybeFoo is allowed to be force-unwrapped like this.

Postfix-! with a comment

let foo = maybeFoo! // an invariant condition has been violated

This is marginally better than a bare force-unwrap, except that it'd be nice to have that explanation in the logs if this ever fails. That makes tracking down problems easier, and gives me a leg up on triaging crashes.

guard let

guard let foo = maybeFoo else { fatalError("an invariant condition has been violated") }

This is a better approach than a bare force-unwrap, and for the occasional optional, this may be fine.

But once you start dealing with lots of IBOutlets (my style is to never use implicitly unwrapped optionals, for the same reason that I never use postfix-!), it gets fairly tedious.

So it marginally passes the expressivity test, but fails finding a balance with terseness.

?? fatalError()

let foo = maybeFoo ?? fatalError("an invariant condition has been violated")

This definitely terser, but using ?? (which never crashes in any other context) feels like the wrong approach.

!! fatalError()

let foo = maybeFoo !! fatalError("an invariant condition has been violated")

Using ! over ? in the operator is semantically more appropriate, but you again fail the terseness requirement (and the desire to Make Swift Get Out Of Your Way™) if you need to use this more than about 3 times. Repeating fatalError("this is why") everywhere gets pretty old pretty fast.

Thus...

Infix-!!

let foo = maybeFoo !! "an invariant condition has been violated"

This is a great balance between expressivity, terseness, debugging aids, and readability. The operator would use fatalError and not preconditionAssertion, because the latter gets compiled out in certain builds, which defeats the entire "debugging aid" goal.

It's nice to also have !! () -> Never, if you come across a situation where you want to do additional logging/capturing/whatever as you crash, so that you can supply your own wrapper to fatalError().

4 Likes

@davedelong would you or any other proposal authors take a few second to comment on the reasoning behind the choice of !! operator instead of ?! like I mentioned above already twice. The left ! would actually mean force-unwrapping the lhs parameter while it really isn't. I'd really appreciate an answer to that question.

As I noted above !! is just symmetrical to ?? while above you explain what ? and ! means in a different way. In that situation I don't think we should push symmetry over actual semantics of the operator.

Personally to me, this seems like the most important motivation of the proposal. The writing of the proposal itself gets bogged down with the fact that Xcode is still producing the x! fixit instead of x ?? <#default#> fixit, and I found that to be distracting and beside the point.

If we were motivated to solve the case above, I would ask this set of questions (to be clear, none of the names suggested below are serious proposals):

  1. How common is this case to be worth sugar?
  2. How prominent and common are "good" uses of x! (in contrast to the bad ones like Xcode is pushing people towards)? I know that NSURL and other APIs make x! the right thing, but are these common enough to warrant a change?
  3. If we are going to sugar the above (very verbose!) code, why go all the way to an operator? Why not go with a method on optional?
  4. If the above is important enough to sugar, then why aren't all the other trapping operations, like try!, as! and array subscripts? If our goal was to improve those, then a library feature that installs a thread local failure string would be a better solution IMO, that would allow: let x = failureMessage("reason why x can't be null") { x! }. You could even use an autoclosure to get rid of the punctuation soup there.

To elaborate on point 3 above, the entire point of the x! syntax is to be concise and to support chaining of expressions like x!.foo(). Including an error string defeats the rationale for having a terse operator: the proposed !! operator isn't really useful except at the top level expression. Chaining with it would require parens, and the logic would be pushed to the right of the string. Given that, something like this: let x = x.forceUnwrap("reason here") seems to achieve the same goal, allows reasonable chaining, is more clear, and is still less verbose than the thing you're trying to fix above.

To reiterate my position on the original post, the proposal as written seems odd in that it is a partial solution to sugar an operator that many style guides say not to use - and those style guides don't generally say "write guard + fatal error" AFAIK, they say to use x? or ?? or other existing mechanisms. IMO, the introduction of new operators should have a very very high bar, because they directly add "language complexity" that every Swift programmer will be expected to memorize. If this were important to address, it seems that adding a method would be a better way to go for clarity of code, and to avoid putting additional language weight into something we don't really want to encourage use of.

-Chris

15 Likes

I can't speak for the others, but in my code, I use ?! to indicate something that is "more dangerous than ??, but more recoverable than !!"

They go (in terms of recoverability) from:

?? → no recoverability issues. no crashing. no errors. Use the left if possible, otherwise use the right.

?! → some recoverability issues. no crashing, but throws an error defined by the right if the left is nil:

public func ?!<T>(value: T?, error: @autoclosure () -> Error) throws -> T {
    if let value = value { return value }
    throw error()
}

Usage example:

public extension JSON {
    
    public func value<T: JSONInitializable>(for key: String, path: String? = nil) throws -> T {
        let o = try self.object ?! JSONError.wrongKind(self, expected: .object)
        ... use o as a strongly typed JSON object value
    }
}

!! → never recoverable. crashes. uses the right to explain why the left failed.

public func !!<T>(value: T?, error: @autoclosure () -> String) -> T {
    if let value = value { return value }
    fatalError(error())
}

Basically "the more ! it has, the more dangerous it is". ?! is therefore more "dangerous" than ??, but less dangerous than !!. To me, that indicates that ?! would throw an error (which is "more dangerous" than nil coalescing, but less dangerous than crashing).

5 Likes

Okay this is your personal preference, but what would speak against two overloads on ?! which can throw or die. I mean this whole proposal is about 'unwrap' lhs (?) or die on rhs (!). Would be interesting to see the opinions of the other proposal authors in comparison.

That's definitely an option, but personally I would be opposed to having a single operator spelling do two different things depending on the type of the RHS. I think that would violate my goal for a high degree of expressivity in code, because I'd have to stop and introspect types in order to know what's going to happen.

1 Like

This seems to be the cause of a big part of the discussion in this thread. IMO it's not ?? that crashes here, it's fatalError. To me this is like saying that

guard let foo = maybeFoo else { fatalError("an invariant condition has been violated") }

is bad because guard isn't "supposed" to crash.

4 Likes

I can see that line of thinking ("?? is sugar for an if ... else statement"), but I disagree with it, mainly because ?? is an operator, and guard... is not.

The comparison fails because of Postfix-!, which is also sugar for an if ... else statement, except it crashes:

let foo = maybeFoo!

is equivalent to:

let foo: Foo
if let f = maybeFoo {
    foo = f
} else {
    fatalError()
}

Yet no one seems to have a problem with Postfix-! causing a crash. Why is !! different?

Terms of Service

Privacy Policy

Cookie Policy