Over the years there have been some requests to shorten something like this:
guard let foo = foo else {
return
}
to something like this:
guard let foo
While I think that syntax is probably a little too sugary, lately I’ve been writing a lot of code that makes preconditions explicit:
guard let foo = foo else { preconditionFailure() }
In general I’m a fan of using precondition() and assert() a lot more than fatalError(). It would be excellent if this were valid:
precondition let foo = foo
// do something with foo
Under the hood, this would do the same as the guard statement, but by using precondition I think it avoids overloading the guard statement. We could also make it look like this:
precondition(let foo = foo)
as long as foo continues to be unwrapped in the scope following that statement.
Using -Ounchecked could crash in this case with an unexpected nil, but I think that would be expected. What do y’all think?
A good argument in favor of flow-sensitive type narrowing in the general case.
However, in this case specifically, if precondition(foo != nil); let foo = foo! is too noisy, then a straight-up let foo = foo! suffices, and if the latter is too terse, then the former provides the desired noise, no? (Not the mention the full guard let foo = foo else { preconditionFailure() } form.)
What I'm trying to say is that the language already has a spectrum of "noisiness" for this. That some of these make (correct) use of the ! operator isn't a problem that requires fixing.
That would require some deliberate work to make possible: foo cannot be nil and then not nil from one line to the next unless it's been captured and shared across threads. Since Optional is a value type, that's provably not the case unless you've just done it to yourself within the local scope. Am I wrong?
In the simplest case, you can see that the compiler generates the same code:
func f(_ x: Int?) -> Int {
guard let x = x else { preconditionFailure() }
return x
}
func g(_ x: Int?) -> Int {
precondition(x != nil)
return x!
}
output.f(Swift.Int?) -> Swift.Int:
test sil, 1
jne .LBB1_2
mov rax, rdi
ret
.LBB1_2:
push rbp
mov rbp, rsp
ud2
output.g(Swift.Int?) -> Swift.Int:
test sil, 1
jne .LBB2_1
mov rax, rdi
ret
.LBB2_1:
push rbp
mov rbp, rsp
ud2
That's a good point. I glossed over the scenario where foo, orthogonal to this topic here about optional unwrapping, is a mutable or computed property such that you'd want to access its current value once and bind the result. It is indeed always good advice to be mindful of that.
When self is shared across threads, then indeed self.foo could mutate from one line to the next; if self.foo is a computed property that's not pure, then each access could give a different result.
I took it as by construction that we were discussing the scenario where we had a unique reference to a value foo, as is the case when it's passed in as an argument. It is important for users to know that foo does not change from nil to non-nil from one line to the next.
But yes, you are right that where actually binding a value up front is important, then it is important actually to bind it up front. In that case guard let cannot be replaced by two accesses. In that case, what's discussed here is then equivalent to let foo = self.foo; precondition(foo != nil) and then using foo! in the scope following that statement.
It is much more in line with Swift's tendency to drop boilerplate and template if it can be inferred. It fits with us writing let x = "String" instead of let x: String = "String" and leaving out the return for a single expression function, method, or computed property.
IMO this feels like extra syntax where something like this would be much nicer: precondition(let foo = foo, "Must have foo")
Although I'm not sure how this would work, as it'd be a much more general approach, and how it'd work in conjunction with (for example) @autoclosure.
To maybe rephrase a bit: why add more "magic words" to the Swift syntax, instead of a mechanism for functions to provide such behaviour. Because if you're adding it for guard & precondition, maybe also add it for assert? Or other such functions I'm unaware of?
I know it's not the answer you're looking for, but just about all of my swift code doesn't have unnecessary empty unwraps. Usually it's a precondition resulting in a non-void return value. When reviewing code, seeing a line like that is code smell for me. If the function takes an optional and returns early without doing anything, then maybe it shouldn't take an optional value in the first place. I've battled with this a few times before, but if it really can't be avoided I always suggest to use that empty line before return to print a log message.
This thread is giving me SE-0217 vibes. I feel that syntax could be quite fitting for this proposal (considering the existence of ?? as a "soft-fallback"):
let foo = foo !! preconditionFailure("Foo must not be nil")
This also allows flexibility for other cases such as:
let foo = foo !! fatalError("Foo must not be nil")
And in general, any function yielding -> Never.
I am aware of the decision to reject the original proposal 3 years ago, however most of what was said to be the reasons for it back then has fallen short by now and we're still dealing with this same issue in Swift nowadays, so I feel a review of this situation is due.