Follow along at https://www.hackingwithswift.com/100/12.
After Tony Hoare invented null references in 1965, he would later refer to them as a "billion-dollar mistake". I disagree. The concept of something not having a value can be just as important as the concept that it does. And in my opinion, good language design isn't about constraining what a programmer can express so much as it's about constraining how they express whatever they might be thinking.
"Nil", "null", "None"... these notions are unavoidable. It's up to the language to design a way to expose them.
Enter Swift optionals — a way of handling the inevitable, totally-non-optional concept of nil
in an explicit, type-safe manner.
Because an optional value contains nil
in the absence of anything else, these two forms of initialization are equivalent.
var mass: Int? = nil
print(mass) // nil
var mass: Int?
print(mass) // nil
Things get interesting when an optional does gain a value, though:
var mass: Int?
mass = 100
print(mass) // Optional(100)
If you're coming to Swift from a non-type-safe language where null
or nil
is tossed around glibly, this can be a lot to take at first sight. Do a bit of unwrapping, though, and you'll find that Swift's interface for interacting with optional values — and potentially, null references — is pretty sweet.
func generateMass() -> Int? {
...
}
func displayMass() {
if let mass = generateMass {
// Do something with the unwrapped Int inside of `mass`
} else {
// Do something when `mass` is nil
}
}
func generateMass() -> Int? {
...
}
func displayMass() {
guard let mass = generateMass else {
// Do something when `mass` is nil before the the function exits entirely
}
// If we get here, `mass` is a first-class `Int` with a value
}
Guard statements are actually part of a larger paradigm: Running an expression inline with the current block of scope, and then exiting the block if it doesn't execute as expected.
This can be a really clean way to flatten code that performs some initial checks in a function. I'm trying not to abuse it, though 🙂.
To me, guard
has a special meaning — much closer to assert
. It's an indicator that something generally should be the case, otherwise we don't want to be in the function at all.
Conversely, with if/else
, we're genuinely not sure what the case might be — but our function intends to handle it regardless.
Anyway, did I mention this was a larger topic? Onward!
By using !
after an Optional variable, we're telling the language to use the value that it holds — or crash!. This is handy way to get around using if/else
or guard
when we have absolute certainty that an Optional hold a value. But do we really? 😉
Similar to force unwrapping, this applies to declarations that don't initialize a value (and are therefore nil
). It can be thought of as a guarantee to the complier that the variable will have a value by the time we need it.
var mass: Int! = nil // "I promise not to use this until it has a value."
??
lets use the value inside of an optional — or a default withing an expression.
This seems like somewhat of a cousin to the ternary conditional operator (?
), which I often find myself using when the value corresponding to false
is a fallback.
This is a nice way to conduct chained property access when something along the way is an optional:
let nameLength = person.nickname?.count // returns nil or `count` wrapped in an Optional
The only caveat, though, is that it still tries to unwrap the value used with ?
as an Optional — so I'd think of it as more of "safe access" than force unwrapping, which was my initial intuition.
Failable initializers are basically a way to declare your entire type as being an Optional. 🤔 I don't know... at that point it feels like we might really need to start questioning our design. But I'm open to seeing some legitimate use cases if they exist.