Swift's Optional equitability
Optional equitability is one of those aspects of Swift that seems simple at a first glance but that makes you stop and think twice when you encounter it in actual code. The good thing is that once you pause and look at it you realise is trivial.
As always, let’s start with an example type:
struct Object {
let flag: Bool
}
Let’s pretend that your code receives an optional instance of this type: Object?
. Optional equitability becomes important when in your code you want to do something with the flag directly like so:
if object?.flag {
...
}
flag
is a boolean so you may expect that to work, and in other languages it might, with some rules you have to learn of it behaviour and its corner cases. But in Swift the compiler will forbid this dangerous code giving you this error message:
Optional type 'Bool?' cannot be used as a boolean; test for '!= nil' instead
That’s an unfortunate error message because its suggestion has nothing to do with what you want. Yes, you could bind the object to a new variable with if let
but that introduced new names into your code that only increase the cognitive overload while reading the code.
But don’t worry, you can convince the compiler to treat that expression as a boolean. You just need to explicitly use the equitability operator ==
:
if object?.flag == true {
...
}
The best way to understand how this works is to analyse all the cases that this interaction has. Remember that you are not dealing with a boolean here, but with an optional boolean. This means you have three cases: true
, false
and nil
.
If we declared the cases as variables they would look like this:
let oT: Object? = Object(flag: true)
let oF: Object? = Object(flag: false)
let oNil: Object? = nil
Now let’s try all the equalities and see their results:
oT?.flag == true // true
oF?.flag == true // false
oNil?.flag == true // false
oT?.flag == false // false
oF?.flag == false // true
oNil?.flag == false // false
oT?.flag == nil // false
oF?.flag == nil // false
oNil?.flag == nil // true
With these examples in mind I hope you can now see how it works. A good mental model to follow is to think that what the compiler is doing is promoting the literal to an optional and just comparing those.
In our case we could rewrite the example to make it more obvious:
if object?.flag == Optional(true) {
...
}
Once you realise that you are dealing with optionals, and not boolean, in both sides and that you have three cases instead of two, the results become obvious.
But even so, I would like you to take a closer look at one specific combination:
oNil?.flag == false // false
This just says that nil
is different than false
. This is obvious because they are two different cases of the three we have available. But if you are used to other languages, this may trip you out since you could imagine that false
and nil
are equivalent. Be careful with this one.