Why is switch case optional binding syntax the way it is?

Whenever it's been a little while since I used a switch case to bind an optional, I always, without fail, get it wrong the first time. e.g. the correct syntax is:

switch someOptional {
case let value?:
    …
default:
    …
}

…but I always instead intuitively reach for:

switch someOptional {
case let value:
    …
default:
    …
}

Not only does this not work as intended - that case will match any and all values, including nil - but the compiler doesn't offer so much as a warning about it (maybe it'll issue a warning about what it thinks are redundant other cases, if they happen to be completely overshadowed).

(I think it should warn about this because, at least in simple cases like the above example, there's absolutely no point to the switch statement since only one case is ever reachable)

To me the 'correct' syntax is exactly opposite of what I expect. Appending a question mark suggests to me that the value can be optional.

This 'correct' syntax is also the opposite of that used in e.g. guard statements, where the question mark is not included:

guard let value = someOptional else { … }

Adding a question mark there is a compiler error.

And in fact you can use case syntax in a guard statement to similarly get very unintuitive results:

guard case let someOptional else {
    …
}

The above guard statement will never function, and the compiler doesn't even issue a warning about it.

(again, this feels like it should be a compiler warning at least - the compiler knows the guard statement has no function)

"In English", as it were, inserting "case" doesn't in any way change the apparent meaning of the statement - an unfamiliar reader could easily just assume that it's the "full" version of the syntax, and Swift merely allows the redundant case keyword to be omitted for brevity.

I'm not necessarily critiquing the syntax as it is - rather, I'm hoping someone can rationalise this unintuitive & seemingly inconsistent syntax to me. I'm hoping that if I understand it better my brain will finally permanently remember the correct syntax.

2 Likes

It’s really the opposite. Early in Swift’s language development, there was some agreement that the optional-checking syntax in if and guard should really be if let x? = y to reinforce the connection with pattern-matching and optionality. However, we’d already implemented the more magical if let x = y, and adding a character to that felt like a regression, so it was a hard sell.

The pattern-matching syntax has to have some character for unwrapping an optional because it’s a recursive syntax — if you have an enum case with an optional payload, you want a pattern like case .myCase(let v) to just check whether the value is in that case, not to also check whether the payload is non-nil.

4 Likes

Perhaps some of the confusion stems from the difference in context. In guard let foo you're asserting that foo is not nil. Similarly for if let foo where you're saying "iff foo has a value…". There's no uncertainty about what you're looking for, so a question mark would be misplaced.

Same as in English - you don't say "If there's a ball in the bucket? I will grab it". That's technically clear (enough), but weird. You'd just say "If there's a ball in the bucket, I will grab it".

case let (foo?, bar?): reads (to me) as more like a variable declaration, where a question mark (on the type) makes it optional. It reads like it's saying "maybe there'll be a foo and/or bar, or maybe they're nil". The more complicated the 'tuple' in the case statement the more that's the case.

I can somewhat understand the rationale for using a question-mark in the opposite way, I just don't find that reasoning intuitive at all.

But thanks @John_McCall for providing some history. That explains the discrepancy between case statements and let-binding in conditionals. And hopefully it'll help me deduce the correct syntax next time. :smile: