SE-0217 - The "Unwrap or Die" operator

Proposal rejected

The core team has decided to reject this proposal as written. However, the core team concurs that the motivating problems posed by the proposal are important to solve, as did an overwhelming majority of commenters who participated in the public review. The fact that the only fixit the compiler offers to unwrap an Optional is to use the ! operator is an unfortunate legacy of the Swift 1.0 days, before anything in the SDKs Swift was designed to work with had been audited for nullability, so force-unwrapping was far more of a necessity. Nowadays this legacy is actively harmful, and encourages bad habits in new Swift programmers, as the proposal and review discussion highlight extensively. It is clear too that ! giving inadequate runtime feedback is a major problem, since a large contingent of the Swift community follows style guides that flat-out ban it, and the guard ... else { fatalError("message") } idiom is widespread as a way of more thoughtfully crashing on nil with an actionable error message.

All that said, there isn't a clear case that adding a new operator to the standard library is the best way to address these concerns. Many issues with the proposed design were raised in the review, including:

  • Adding operators, in particular operators without term of art precedent, in general has a high bar. Operators, while compact and convenient to use once learned, are harder to learn, being more difficult to search for, talk about in speech, or query in IDEs than named functions.
  • !! suggests an expansion of the taxonomy of ?/! operations that doesn't pay off—if one thinks of it as an extension of ! with a message, it suggests the existence of try!! and as!! variants with messages too.
  • Syntactically, !! doesn't work as fluently as !. Being an infix operator, it would require parens to chain additional method calls onto the unwrapped value, as in (x !! "message").foo().
  • !! is visually similar to ||.

The core team believes that this entire discussion would be much different if the tooling around the language were improved independent of language evolution. It is clear that the ! fixit is harmful and counterproductive; that must be fixed in the compiler independent of the language design. The compiler shall instead offer a choice of fixits, including at least the possibilities of x ?? <#default value#>, x? (where that is possible), and x! (with a note to use it only when certain the value can't be nil in the fixit note). Doug is working on implementing this improvement. Changing the fixit will make it no longer possible for "fix all in scope" to thoughtlessly pepper code with force-unwraps, and it will make it plain to the user that they must think about what they need to do in response to the nil value at that point. The core team believes this will go far in mitigating the misuse and bad practices promoted by the fixit as identified by the proposal.

Meanwhile, elsewhere in swift-evolution, the core team has also accepted SE-0215 — Conform Never to Equatable and Hashable, and came out of that discussion strongly in favor of investigating making Never be a subtype of every other type. This requires its own design cycle, but it fills in a general gap in the type system, and would enable the idiom x ?? fatalError("message") without any additions to the standard library. This pairing of ?? with Never was also discussed thoroughly in review, and in turn has its own share of criticisms:

  • It has some of the same drawbacks as !! would: there's the same lack of syntactic fluency, requiring parens to chain with further method calls. Also like !!, there's no corresponding try?? or as?? operator.
  • It raises valid concerns for teachability. Although someone familiar with the language could reason about it from first principles, when taken as an idiom by a beginner, it appears to imbue ?? with an alternative meaning "crash with message", which is at odds with the general taxonomy of ?/! operations. Furthermore, since ?? would allow an arbitrary Never-returning expression on the right-hand side, it also presents a crisis of choice on what function to put on the right-hand side that is its own candidate for confusion—fatalError? preconditionFailure?
  • Although more compact than guard let ... else { fatalError("...") }, x ?? fatalError("...") does not reach the terseness possible from a dedicated unwrap-with-message operator or method.

Nonetheless, similar idioms are well precedented in other languages, such as Perl and Ruby's x or die, Rust's x.unwrap_or_else(|| return) (which was recently sugared as Rust's postfix ?), and Kotlin's x ?: throw, and the expressivity of the idiom is well-liked in those communities. The review discussion raised the possibility of adding an overload ??<T>(_: T?, _: Never) -> T as a stopgap, but in experimenting with this solution, we found that overloading had unacceptable impact on type-checking performance, and adding the overload presents a non-zero risk that, if making Never be a universal subtype does not end up happening for whatever reason, then the overload on ?? would be left as a strange special case in the standard library.

The core team does not think this has to be the last word on this subject, but we would like to see how usage of the language in practice changes once the steps of improving the optional unwrapping fixits and implementing implicit conversions for Never are taken. If there's still widespread confusion and misuse of optionals, we are willing to reevaluate.

Thank you to everyone who participated in the review!

37 Likes