SE-0217 - The "Unwrap or Die" operator

Not appropriate for the general case. For one thing, it doesn't work in the situation where your chaining contains more than one unwrap, especially if one of them is not a force-unwrap:

var result = someObject!.parent?.run()

For another, run() could itself be returning an optional and you'd be force-unwrapping the result of run().

Beside the subtle change in semantics on what situations can make the unwrap fail, it also moves the message further away from the cause of the problem you want to detect. I find this makes the code less readable.

This is very interesting, thanks!

I remember that when I was writing sample code with !! above, I indeed had to pause and wonder if the string should describe the expectation or the failure. I was slightly confused as a matter of fact. Should I favor code legibility or error reporting ??

But your post reminds us that this conflict vanishes when the !! message reads as documentation why the value is not nil (describe the expectation), and is negated in the error message (since the expectation has failed).

Paul, I wonder if the proposal could be enhanced with some clarification about this (and maybe checked for consistency).

1 Like

The proposal is designed the opposite way, at least in the detailed design/implementation and most of the examples. The symbology of the proposed operator also deliberately follows ??, implying “unwrap or …” semantics (from the proposal: “It matches and parallels the existing ?? operator”), which would suggest negatively-phrased strings. In my opinion, if you want the message to instead read as documentation for why the value should not be nil then the syntax should be very different.

You're right, but not only :-) Many examples are negative indeed, but there is also emphasis on replacing // doc with !! "doc"

Don't you think there is conflict here, which needs to be clarified? The review phase is the perfect opportunity for that.

1 Like

Now, my humble opinion on the positive/negative way to phrase the !! string is to choose the documentation side:

// fatalError: "failed assumption: value has been set somewhere else"
value !! "value has been set somewhere else"

I suggest this because of my bias towards documentation and my expectation that !! becomes popular in sample code, tutorials, courses, etc. In these situations, you want the whole code to be oriented in the same direction. You don't want a negative sentence to introduce confusion:

// brain violence
value !! "value has not been set somewhere else"

This is just my way of thinking about the feature (and about code legibility and fluidity in general).

This way of thinking is also further argument for not confusing !! with ?? fatalError(...):

?? indeed reads as "unwrap or ..." (as you say very thoughtfully, @jawbroken). If we want to favor positive sentences (my humble wish), we need a distinct operator that reads "force unwrap because ...", which is !!.

Edit: sample code:

// Two valid options: pick your favorite
value !! "value has been set somewhere else"
value ?? fatalError("value has NOT been set somewhere else")

In that case it becomes hard for me to understand why the spelling is !!. The right side of ?? is the resulting value when the unwrap fails, but in your version the right side of !! would be a description of why the unwrap should succeed, so they seem opposite in polarity to me. You then have to negate the message by prepending “failed assumption:” to it. So I think that the syntax should be something different to !! if the messages are going to be phrased that way, because !! should be the force-unwrapping parallel of ??.

This confusion, present even throughout the proposal itself, is really working to convince me that the shorthand version is not valuable enough to be worthwhile, and that it would be better if everyone instead had to write fatalError(…), preconditionFailure(…), assumptionFailure(…) (a hypothetical version of fatalError that prepends “failed assumption:” to the message), etc. That further implies that !! probably doesn't hold its weight, because it does the same thing as ?? with the proper treatment of Never.

1 Like

If Never is in the picture, I don't really see the need for the !! operator and would actually prefer using ??. I think it would be clearer and less surprising to users.

8 Likes

@jawbroken. I agree with you that the proposal is still too confused on that matter. Let's be forgiving, because authors did not have time to polish it before the review.

That's why we should try to move forward and help them (assuming the proposal has any value, regardless of its eventual acceptance, refusal, or deferral).

In that case it becomes hard for me to understand why the spelling is !!. The right side of ?? is the resulting value when the unwrap fails, but in your version the right side of !! would be a description of why the unwrap should succeed, so they seem opposite in polarity to me.

Yes. This is why I'm happy we have distinct operators: we can decide that they mean something different, and that they are as opposed as if / unless, and expectation / failure:

// Two valid options: pick your favorite
value !! "value has been set somewhere else"
value ?? fatalError("value has NOT been set somewhere else")

You then have to negate the message by prepending “failed assumption:” to it.

A logical consequence of the positive documenting phrasing: the !! assumption has failed.

So I think that the syntax should be something different to !! if the messages are going to be phrased that way, because !! should be the force-unwrapping parallel of ??.

This is where we diverge: I suggest that we are free to define !! as we want, since it is a new operator. If the proposal says “It matches and parallels the existing ?? operator”, then it's only a consequence of the current confusion: we don't have to take it for granted (assuming we want to help).

This confusion, present even throughout the proposal itself, is really working to convince me that the shorthand version is not valuable enough to be worthwhile, and that it would be better if everyone instead had to write fatalError(…) , preconditionFailure(…) , assumptionFailure(…) (a hypothetical version of fatalError that prepends “failed assumption:” to the message), etc.

But... Wouldn't it be source-breaking if fatalError etc. would start negating their string??? I must have misunderstood.

1 Like

Oh, I didn't mean it like that at all, sorry. I only meant that I think it is probably inherently confusing to just have a string on the right hand side, because there's at least two possible ways that the same assumption can be phrased. I was just using the proposal as possible evidence for that, not suggesting that it was due to a lack of care.

It's possible to define any semantics for the new operator, sure. The motivation from the proposal suggests a natural interpretation of !! as the parallel of ?? though, and I think that it would be confusing if that wasn't the case. It would diverge from all the other places in the language where ? is replaced with ! to form the force-unwrapping parallel.

To clarify, I was suggesting that if you prefer to phrase things as positive assumptions then you could define a function called assumptionFailure(…) or similar that would prepend “failed assumption:” to the message, and use it in the place of fatalError and friends.

2 Likes

To sum up my current position, inspired by all excellent contributions so far:

  1. Both !! and ?? Never are valuable options when a dry ! has a negative impact on code clarity. They can also avoid some distracting guards. I'll happily use both, just as I happily use if and guard in Swift, and if and unless in languages that provide both.

  2. !! reads "force unwrap because ...".

  3. ?? Never reads "unwrap or ...".

  4. !! "assumption" is equivalent to ?? fatalError("NOT assumption") (note the opposite sentences that describe the assumption, or the failure). One can also write the same MUST-sentence in both forms. One can choose one or other form and phrasing, depending on one's taste, the context, and the level of danger that the assumption may break in the future:

    // pick your favorite
    value !! "value has been set somewhere else"
    value ?? fatalError("value has NOT been set somewhere else")
    value !! "value MUST be set somewhere else"
    value ?? fatalError("value MUST be set somewhere else")
    
  5. The !! / ?? Never pair exemplifies progressive disclosure: one does not have to be exposed to the non-trivial Never type until one can/wants to.

  6. !! "assumption" lets developer describe the expected reality, or a true fact proved by the context. This is is good for legibility, fluidity, and self-documenting code. I expect it to become popular in educational code, which will appreciate avoiding the potential for cognitive dissonance induced by sentences that describe the unexpected/untrue.

  7. Because !! "assumption" has educational value and is on the easy side of progressive disclosure, it is better than ?? fatalError(...) in the new fixit (a major part of the proposal, as @beccadax has reminded us).

  8. When !! fails, the assumption message is prepended with something like "assumption failed:". The resulting error message ought to be fully reported in crash reports. Should preconditionFailure be preferred over fatalError as the default Never function behind !!, we should work around SR-905.

4 Likes

-1.

It appears that the proposal is considerably motivated by these premises:

Requiring a string on the rhs of !! provides useful information all the way from source to the console should the underlying guarantee fail at runtime.

these explanations are not emitted when the application traps on the forced unwrap

My experience is that tools and symbolicated crash reports are generally able to pinpoint the crash site for us the developers, and by extension in-code documentations close to the crash site (which could also be self-explanatory) would naturally be brought to attention. If they fail to do so, it is a tooling issue, and we might want to avoid resolving a tooling issue by introducing a new operator in the language.

Moreover, the proposal did not evaluate a potential side effect of such operator — strings for such developer docs have to be shipped as part of binaries, and it gonna stack up as more dependencies across the community adopt such approach (officially endorsed by the language, if the proposal is accepted). If we assume the tools are doing their jobs, the information should have been fairly accessible, and it is perhaps of dubious value to pack these "additional crash contexts" in binaries solely for rationales stated by the proposal.

7 Likes

I also thought I would be strongly for this when i started reviewing, now I am certainly less clear from reading comments.

Backing up, and looking at how I want to deal with a fatal situation while force unwrapping. I think the inherent terseness of ! is a problem, it is too easily added, and if you are in a hurry trying to compile something sometimes it is often just thrown in there, lurking to eventually cause a crash. I would like this proposal to essentially make a replacement for ! in most cases. Linters would look for single ! and suggest other alternatives.

Now if we go with !!, having to specify, potentially a long string, every time you need to access something that may fail, we have maybe gone too far the other way. The happy path is no longer the emphasis and the negative path becomes overwhelming.

Comparing this to the alternatives such as using guard let this is definitely a step in the right direction, having to have curly braces, indents, and fatal error are quite cumbersome. Many are inclined to not bother with it because it takes more time to write, and it interrupts the flow of the code with indents etc. So from that perspective, I think it is better than the options we have now.

So I think we definitely need to have a single line solution that does not require braces, which is accomplished with both !! and ?? fatalError("message") The question which is best of these, and is the failure case getting too much of the attention? From this perspective the !! is the most terse, and the `?? fatalError("message)" is more clear.

I think I would be happy with either of these solutions, but I am left with, is there a solution that could make the happy path not subordinate to the failure path. Maybe not having a string there would be better? What if had an enum with our failures? This would make it so you could move the error description up out of the main line of code.

enum Failure: Fatalities {
case urlMustNotBeNil
case expectedFileIsMissing
}

let url = URL(string: "messedupurl") !! .urlMustNotBeNil
let file = Data(contentsOf: "missing file") !! .expectedFileIsMissing

Fatalities would be a protocol, and then get automatic completion so all you would need is the .urlMustNotBeNil this would be similar to the Error protocol. The !! would only allow the Fatalities protocol. (I have not name-smithed the protocol name). I think this fits more in the spirit of the exception handing in Swift. This also makes the solution even more terse without giving up the clarity of fatalError thus balancing out the happy path, and the failure path as equals. Also, if you have the same reason more than once, you don't have to repeat the same string over and over. If you want more detailed explanations the enum can implement string

enum Failure: String, Fatalities {
case urlMustNotBeNil = "URL Must Not Be Nil"
case expectedFileIsMissing = "Expected File Is Missing"
}

TLDR: So, this is a suggested change to the proposal. The main change, just allow implementers of the Fatalities protocol on the right hand side of the !! rather than a string. It would work similar to Error protocol.

  • Paul
1 Like

One other thought, perhaps allowing this for implicitly unwrapped optionals would be desirable too.

class ViewController: UIViewController {
@IBOutlet weak var myButton: UIButton !! .notWiredInStoryboard
}

Thinking more...what if the Failures protocol, was just the Error protocol rather than adding another protocol? Does that muddy the waters? This way you are able to use existing Errors and maybe this will help, you do the right thing in the future, not using force unwrap, if you are just doing it as a stopgap in a hurry. This makes you want to write your error code by making it easier than just writing a "quick and dirty" string and later fill in the blanks when you have time to do your error handling properly.

Hello,

I think that the same reasoning is behind SR-905. Especially this comment:

Binary size is very important. You might be shipping the app to millions of users. Bigger applications don't just mean longer download size. They also mean less space for data that is important for the users.

It is not the host application that needs to get the error description, it is the developer. And developers should be able to use better tools. If we design the language around limitations of the current tools, we won't arrive at a better language.

I can totally understand that.

But there is an adversarial consequence of this way of thinking: developers just don't give a dime about it because they need verbose crash reports today. They don't have the luxury to wait until the tooling figures out that they basically earn their living fixing bugs. They thus use fatalError so that their useful diagnostic strings are firmly embedded in the binary, and properly outputted in the crash reports.

This does not only affect lousy developers that should look for another job, but also reasonable library developers who want that preconditions output proper diagnostic whenever the library is misused.

Do the standard library designers know that we can not use apis like precondition() in serious code if we want proper diagnostic? How does it feel to ship useless apis? Come on, give bloody SR-905 a look!

@Anders_Ha, your perfectly sensible reasoning punishes developers by hiding the crucial information they need, until they simply work around it. It thus has, I'm sorry, a very low net effect. So say it less bluntly: it needs serious amendment to make it useful.

1 Like

Update: lines are moving :tada: Promise, I'll stop complaining now. @beccadax wrote in SR-905 :

Just gonna drop a couple “third ways” here which might get us code size or secrecy gains without losing descriptive assertions:

  1. Compress assertion strings.
  2. Replace assertion strings with small, opaque integer IDs, put a table of them in the debug info, and look up the assertion text when you symbolicate.

@Anders_Ha would you change your mind if such an optimization were eventually available for !! ?

Regarding the previous comments, I'll also add that (supposedly) Swift is a general-purpose language not limited to app development. Those "binary size" considerations that app developers may care about don't really affect me on the server. I respect that app developers are a big audience of Swift, but not including a feature because it might be used in a way that raises issue with a subset of Swift's audience doesn't seem reasonable to me.

6 Likes

One thing I just noticed is that the rhs of !! above is always a string literal. With this, the signature of !! would be func !! <T> (lhs: T?, rhs: FatalErrorReason) -> T where FatalErrorReason: ExpressibleAsString(Interpolation)Literal.
This could provide simplifications to the mental model of !!, and possibly counter some people's objections to having a "string" on the rhs.

1 Like

-1 (Sugar No)
Have read proposal and reviewed most of the responses to this thread.

This is no better than applying an adhesive bandage to the ! operator, and won't turn the tide in terms of it's misuse, primarily because it still obfuscates what this implies.

IMO a more measured improvement would be have the fixit rather always propose the unsugared version:

guard let value = wrappedValue else {
  fatalError(<# "Explanation why lhs cannot be nil." #>)
}

In summary:
IMO its preferable that no fixit ever actively propose the usage of the ! operator, or another sugared alternative that can just as easily be abused -- hence the lack of any fixit proposals should revert the use of the ! to a more conscious decision. Tying the !! operator to the fixit is what I think should be avoided. As a conscious choice operator (as opposed to fixit) I could appreciate the merits of !!.