[swift-evolution] [Pitch] Introducing the "Unwrap or Die" operator to the standard library

Dave DeLong delong at apple.com
Thu Jun 29 10:13:04 CDT 2017


> On Jun 29, 2017, at 1:05 AM, David Hart via swift-evolution <swift-evolution at swift.org> wrote:
> 
> I’ve taken time to digest all the messages on this discussion and would like to summarise my point of view concerning several topics:
> 
> Usefulness of messages
> 
> Xiaodi seems to question the usefulness of attaching more information to the failure case of an optional's unwrapping. To his credit, the original example ("Array guaranteed non-empty") don’t add much. Instead, I think we should see those strings as a great opportunity to add application-specific business logic context that help debugging when the unwrapping fails. For example, let’s imagine that I am handling the log out operation for a user I know exists, I could write this:
> 
> let user = database.users[userId] !! “User to logout does not exist”

To expand on the usefulness of these messages, here’re some scattered examples of how I’ve been using this operator in an app I work on, pulled from various parts of the code:

// in a right-click gesture recognizer action handler
let event = NSApp.currentEvent !! "Trying to get current event for right click, but there's no event”

// in a custom view controller subclass that only accepts children of a certain kind:
let existing = childViewControllers as? Array<TableRowViewController> !! "TableViewController must only have TableRowViewControllers as children"

// providing a value based on an initializer that returns an optional:
lazy var emptyURL: URL = { return URL(string: “myapp://section/\(identifier)") !! "can't create basic empty url” }()

// retrieving an image from an embedded framework:
    private static let addImage: NSImage = {
        let bundle = Bundle(for: FlagViewController.self)
        let image = bundle.image(forResource: "add") !! "Missing 'add' image"
        image.isTemplate = true
        return image
    }()

// asserting consistency of an internal model
let flag = command.flag(with: flagID) !! "Unable to retrieve non-custom flag for id \(flagID.string)"

My usage of “!!” generally falls in to two big buckets:

1. Asserting system framework correctness

	For example, the “NSApp.currentEvent” property returns an Optional<NSEvent>, because there’s not always a current event going on. That’s fine. But when I’m in the action handler of a right-click gesture recognizer it is safe to assert that I do have an event. If this ever fails, I have an immediately clear description of where the system framework has not worked according to my expectations.

2. Asserting app logic correctness

	For example, I use this to assert that my outlets are properly hooked up (and the message tells me explicitly which outlet I’ve forgotten), or that my internal data model is in a consistent state.

Both areas of usage have been extremely helpful in building my app. They help me identify when I forget to put resources in the right target, or when I make changes to the internal model but forget all the places I’m supposed to insert things. They help me catch when I fat-finger a URL.

Yes, I could absolutely have done all of this with just a bare unwrap operator, but by putting the diagnostic message in there, I get immediate feedback as to why my code is failing. I don’t have to go digging around in the code in order to re-teach myself of what invariants are supposed to be held, because the error message gives me the succinct and immediately-actionable thing to do.

> Never and new operator
> 
> If we introduce the new operator !! solely with the String override, I still have some doubts about it pulling its own weight. Of course, we could add a () -> Never override to increase its usefulness:
> 
> let user = database.users[userId] !! “User to logout does not exist”
> let user = database.users[userId] !! logFatalError(“User to logout does not exist”)

As I demonstrate above even just the string version can be extremely helpful.

> But Jaden Geller makes a very good point: if and once Never becomes a true bottom type, that syntax will be redundant because Never will be usable with the ?? operator, creating a lot of confusion:
> 
> let user = database.users[userId] !! logFatalError(“User to logout does not exist”)
> let user = database.users[userId] ?? logFatalError(“User to logout does not exist”)

Maybe the answer then is to add !! for strings, and then use ?? If you have a custom Never function.

> Those two lines will have exactly the same effect.
> 
> Cognitive Dissonance of Never and ??
> 
> Ben Cohen originally mentioned that if we introduce a () -> Never overload of the ?? operator, there will be cognitive dissonance because the question mark operator is never used in Swift to signal a trap. But if we make Never a true bottom type, which has a lot of advantages, this dissonance will be unavoidable. Shouldn’t we embrace it then?
> 
> Potential for confusion with multiple per line
> 
> This might not be a real concern, but are we introducing a syntax that will make it possible/encourage people to write less than readable code?
> 
> let user = (database !! “Database is not operational”).users[userId !! “User was not set in time”] !! “User to logout does not exist"

IMO, this is a bit of a red herring, because you can already write similarly atrocious code in Swift today. The addition of “!!” wouldn’t really change that.

Cheers,

Dave

> David.
> 
>> On 28 Jun 2017, at 22:30, Erica Sadun via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 
>> Based on the feedback on this thread, I'm coming to the following conclusions:
>> 
>> `!!` sends the right semantic message. "Unwrap or die" is an unsafe operation. It is based on `!`, the unsafe forced unwrap operator, and not on `??`, the safe fallback nil-coalescing operator. Its symbology should therefore follow `!` and not `?`. 
>> 
>> The `!!` operator should follow the same semantics as `Optional.unsafelyUnwrapped`, which establishes a precedent for this approach:
>> 
>> > "The unsafelyUnwrapped property provides the same value as the forced unwrap operator (postfix !). However, in optimized builds (-O), no check is performed to ensure that the current instance actually has a value. Accessing this property in the case of a nil value is a serious programming error and could lead to undefined behavior or a runtime error."
>> 
>> By following `Optional.unsafelyUnwrapped`, this approach is consistent with https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst#logic-failures <https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst#logic-failures>
>> 
>> > "Logic failures are intended to be handled by fixing the code. It means checks of logic failures can be removed if the code is tested enough.
>> Actually checks of logic failures for various operations, `!`, `array[i]`, `&+` and so on, are designed and implemented to be removed
>> when we use `-Ounchecked`. It is useful for heavy computation like image processing and machine learning in which overhead of those checks is not permissible."
>> 
>> The right hand side should use a string (or more properly a string autoclosure) in preference to using a `Never` bottom type or a `() -> Never` closure. A string provides the cleanest user experience, and allows the greatest degree of self-documentation. 
>> 
>> - A string is cleaner and more readable to type. It respects DRY, and avoids using *both* the operator and the call to `fatalError` or `preconditionFailure` to signal an unsafe condition:
>> `let last = array.last !! “Array guaranteed non-empty" // readable`
>> than: 
>> `let last = array.last !! fatalError(“Array guaranteed non-empty”) // redundant, violates DRY`
>> 
>> - A string allows the operator *itself* to unsafely fail, just as the unary version of `!` does now. It does this with additional feedback to the developer during testing, code reading, and code maintenance. The string provides a self-auditing in-line annotation of the reason why the forced unwrap has been well considered, using a language construct to support this.
>> 
>> - A string disallows a potentially unsafe `Never` call that does not reflect a serious programming error, for example:
>> let last = array.last !! f() // where func f() -> Never { while true {} }
>> 
>> - Although as several list members mention, a `Never` closure solution is available today in Swift, so is the `!!` operator solution. Neither one requires a fundamental change to the language.
>> 
>> - Pushing forward on this proposal does not in any way reflect on adopting the still-desirable `Never` bottom type.
>> 
>>> On Jun 28, 2017, at 12:42 PM, Tony Allevato via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>> 
>>> 
>>> On Wed, Jun 28, 2017 at 11:15 AM Dave DeLong <delong at apple.com <mailto:delong at apple.com>> wrote:
>>>> On Jun 28, 2017, at 10:44 AM, Adrian Zubarev via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>> 
>>>> Well the main debate is that, we all want early access to a feature that will be part of Swift as soon as `Never` becomes the bottom type. When this happens the `??` will automatically support the pitched behavior. Until then if we all agree that we should add it now in a way that will not break anything we can simply add an overload to `??` as I previously showed.
>>>> 
>>> 
>>> I believe we should add it now, but I like the recent observation that making ?? suddenly become a potentially crashing operator violates the expectation that ? is an indication of safety.
>>> 
>>> ?? does *not* become a potentially crashing operator. The *fatalError* (or whatever else the user chooses to put there) on the right-hand side is the crashing operation.
>>> 
>>> 
>>> On the other hand, the existing semantics of Swift are that ! is always dangerous, so making !! be the a potentially crashing operator is much more consistent with the language.
>>> 
>>>> There is no need for `!!` because it will fade in the future. If you think of `Never` as a bottom type now then `??` will already make total sense. The default value for T from rhs might be T or Never. 
>>> 
>>> I respectfully disagree with your absolute position on this topic. Even with Never as a bottom type in the future, it would still be more convenient for me to type:
>>> 
>>> let last = array.last !! “Array must be non-empty"
>>> 
>>> … than it ever would be to type:
>>> 
>>> let last = array.last ?? fatalError(“Array must be non-empty”)
>>> 
>>> 
>>> There is a very high bar for additions to the standard library—a new operator added to the language is going to be around (1) forever, or (2) indefinitely with some migration cost to users if it's ever removed. Shaving off a few keystrokes doesn't quite meet that bar—especially when an alternative has been shown to work already that provides the same functionality, is more general (not coupled to fatalError or String messages), and that fits better into Swift's design.
>>> 
>>> 
>>> To make sure I'm not being too much of a downer, I would completely support this broader feature being implemented by that alternative: the ?? + autoclosure () -> Never combo. Then once Never does become a true bottom type, I believe it could be removed and the calling code would still *just work*.
>>> 
>>>  
>>> Dave
>>> 
>>>> 
>>>> @erica: the rhs argument should be called something like `noreturnOrError` and not `defaultValue`. And we should keep in mind that when Never becomes the bottom type we have to remove that overload from stdlib, because otherwise it will be ambiguous. 
>>>> 
>>>> ---
>>>> 
>>>> On the other hand if we tackle a different operator then we should rething the 'default value operator' because the second ? signals an optional but not a non-optional or an inplicit unwrapped operator. In that case I personally thing ?! would make more sense. Unwrap or (non-optional | IUO | trap/die)
>>>> 
>>>> -- 
>>>> Adrian Zubarev
>>>> Sent with Airmail
>>>> Am 28. Juni 2017 um 18:13:18, Tony Allevato via swift-evolution (swift-evolution at swift.org <mailto:swift-evolution at swift.org>) schrieb:
>>>> 
>>>>> It's hard for me to articulate, but "foo !! message" feels a little too much like a Perl-ism for my taste. Objectively that's not a great criticism on its own, but I just don't like the "smell" of an operator that takes a value on one side and a string for error reporting purposes on the other. It doesn't feel like it fits the style of Swift. I prefer a version that makes the call to fatalError (and thus, any other non-returning handler) explicitly written out in code.
>>>>> 
>>>>> So, if the language can already support this with ?? and autoclosure/Never as was shown above, I'd rather see that added to the language instead of a new operator that does the same thing (and is actually less general).
>>>>> 
>>>>> On Wed, Jun 28, 2017 at 8:52 AM Jacob Williams via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>> I feel that the !! operator would be necessary for indicating that if this fails then something went horribly wrong somewhere and we should throw the fatalError. This allows the inclusion of optimizations using -Ounchecked and is clear that this is an operation that could result in a runtime error just like force unwrapping.
>>>>> 
>>>>> If we want code clarity and uniformity, then I think !! Is much better than ?? because it goes right along with the single ! Used for force unwrapping. However, this does depend on if the operator would be returning some kind of error that would cause the program to exit.
>>>>> 
>>>>> I think the ?? operator should not cause a program to exit early. It goes against optional unwrapping principles. I think code could get very confusing if some ? would return nil/a default value, and others would be causing your program to crash and exit. The ? operators should always be classified as safe operations.
>>>>> 
>>>>>> On Jun 28, 2017, at 9:41 AM, Ben Cohen via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>>> 
>>>>>> 
>>>>>>> On Jun 28, 2017, at 8:27 AM, David Hart via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>>>> 
>>>>>>> Count me in as a strong proponent of ?? () -> Never. We don't need to burden the language with an extra operator just for that.
>>>>>> 
>>>>>> You could say the same about ??
>>>>>> 
>>>>>> The concern that an additional operator (and one that, IMO, fits well into existing patterns) is so burdensome seems way overweighted in this discussion IMO. 
>>>>>> 
>>>>>> Adding the operator, and encouraging its use, will help foster better understanding of optionals and legitimate use of force-unwrapping in a way that I don’t think `?? fatalError` could.
>>>>>> 
>>>>>> 
>>>>>> _______________________________________________
>>>>>> swift-evolution mailing list
>>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>>> 
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170629/427817b6/attachment.html>


More information about the swift-evolution mailing list