mokacoding

unit and acceptance testing, automation, productivity

Why implicitly unwrapping Swift Optionals is dangerous

This post is the third and last part of the little series on Swift's Optional type that I've been writing over the past few weeks.

The idea to start writing about Optional came from a code review conversation at my current client:

[Team Member] The !’s make the code read like you’re really excited.

[Gio] The !s are very dangerous, and should be used only in extreme circumstances.

[Team Member] @gio what does ! mean as opposed to ? or nothing at all?

In "What is an optional value in Swift" we introduced Swift's Optional enum, and saw how it is used to wrap values to express their nullability: Optional<Type>.

The cool thing about Swift and Optionals is that if a type is not optional, you can be sure that a value of such type won't be nil.

This code will not compile:

let x: String = nil
// error: nil cannot initialize specified type 'String'

This is pretty cool. It means that if you receive a type that is not Optional you don't have to worry about nullability. This takes away a lot of mental load when writing and reading code, and saves us from always checking for != nil like we did in Objective-C.

In the previous post we also saw that ? is just syntactic sugar for the full Optional declaration.

These lines do the same thing:

let a: Optional<Int> = 42

let b: Int? = 42

The compiler helps us out when dealing with optionals by preventing us from using an optional value as if it was an actual value:

var x: String? = "abc"

x.isEmpty
// ^__ won't compile:
//
// error: value of optional type 'Optional<String>' not unwrapped; did you mean to use '!' or '?'?

We can't call isEmpty directly on our String? value, because we don't know if it's nil or not.

To do so we need to unwrap the actual value from the Optional container, as the compiler error suggests.

Like this:

if let someString = x {
  someString.isEmpty
} else {
  print("Sorry mate, x is nil")
}

Another option is to access it like this:

someString?.isEmpty

The type returned by that code is Optional<Bool>, it carries the optional wrapping with it.

What does ! mean then? The ! syntax allows us to implicitly unwrap optional values.

someString!.isEmpty

The type returned by the code with ! is Bool now, the optional wrapping has been removed by the compiler.

The important difference is that while when using if or guard we are actually unwrapping the value out of the optional container and deal with the case in which it is nil, using implicitly unwrapping we are not.

When you write a ! you're telling the compiler "Dude, chill about these nil, I assure you that there's a value in there. Trust me!". And the compiler like every other good machine will do what its told and proceed to unwrap the !-ed value without needing a check for it.

The problem with this is that these assumptions developers do are more often than not proved wrong, leaving your software to deal with a nil value at runtime. And there's only one result there: it will crash.

Consider this:

let sneakyNilValue: Optional<String> = nil
let x: Bool = sneakyNilValue!.isEmpty

The compiler will consider sneakyNilValue! as unwrappable without needing to check, and compile all right. At runtime .isEmpty will be called on what is expected to be a String, but is actually nil, with this result:

fatal error: unexpectedly found nil while unwrapping an Optional value
Current stack trace:
0    libswiftCore.dylib                 0x000000010a2fd730 swift_reportError + 132
1    libswiftCore.dylib                 0x000000010a319cf0 _swift_stdlib_reportFatalError + 61
2    libswiftCore.dylib                 0x000000010a11e300 specialized specialized StaticString.withUTF8Buffer<A> (invoke : (UnsafeBufferPointer<UInt8>) -> A) -> A + 351
...

This is the reason I told my colleagues "The !s are very dangerous, and should be used only in extreme circumstances.".

Implicitly unwrapping gives you the power to treat optional values as actual values, but from great powers come great responsibilities. You need to be 100% sure that the value you implicitly unwrapped will never be nil, or you're app will crash, and you're users will be unhappy.

Personally I prefer to spend that little extra time to write code to handle nullability and make my app more robust. I don't trust my assumptions, and I don't trust values getting into my code from third parties.

What about you?

If you have more questions regarding Optionals please do leave a comment below, or get in touch on Twitter @mokagio.

All the code examples from this post are available in this script on GitHub, run it to see the crashes and compiler errors we've been talking about.

Leave the codebase better than you found it.

Vote on Hacker News