Date

One of the more contentious issues in Swift is force unwrapping of optionals. Force unwrapping will either give you the value contained within the optional, or if the optional is nil, it will terminate your program. The idea that this code is causing my app to crash is what makes many avoid–or outright forbid–the use of the ! force unwrap operator. Like most things in life, the force unwrap operator is not an unqualified evil. It is a tool that can be used or abused.

There are several of alternatives to force unwrapping:

  1. Unwrap with if let or guard let
  2. Pattern Matching with case
  3. Nil coalescing
  4. Optional chaining
  5. Mapping over with compactMap

With all these options, is it ever a good idea to force unwrap? My answer to this is, “someimes, yes.” Usually on the the alternatives above is the best way to deal with an optional. Even so, I contend that there are still situations when a force unwrap is the best thing to do. When you can prove that the force unwrap will succeed, use it. For example, the code block below force unwraps a URL initializer that takes a string.

class Client {
    let apiHost = "https://api.example.com"

    func request(for item: Item) -> URLRequest {
        let endpointURL = URL(string: apiHost)!.appendingPathComponent(item.path)
        return URLRequest(url: endpointURL)
    }
}

The URL(string:) initializer can fail if the string input is malformed. In the example above, the relevant code is locally contained within the Client class. There is nothing that can change at runtime to make endpointURL invalid. The only way this would fail is if a developer made a change to the code. Then they would get a crash report at exactly the point of failure, making it an easy fix. If you think that a future developer could make this invalid without noticing, make a test case for that possibility.

Things get dicier when you are exposing your parameters to outside input. Here is an extension on Array that can use String indexes. It force unwraps a failable Int initializer that takes a String.

extension Array {
    subscript(_ str: String) -> Element {
        get {
            let index = Int(str)!
            return self[index]
        }
        set {
            let index = Int(str)!
            self[index] = newValue
        }
    }
}

var a = ["a", "b", "c"]
a["1"]

If the string doesn’t convert to an integer, this code will crash. Is it reasonable to expect all users of this subscript to pass valid string indexes? Maybe, but probably not. In this case it’s probably better to avoid the force unwrap. I would rethink the API (does this really belong on Array?) or maybe return an Element?.

There are worse things than force unwrapping. How would you avoid the force unwrap in the first example above? One option is to use a guard let to unwrap the optional, and then fatalError() on the else clause.

class Client {
    let apiHost = "https://api.example.com"

    func request(for item: Item) -> URLRequest {

        guard let apiHostURL = URL(string: apiHost) else {
            fatalError("Unable to make a URL from string")
        }
        let endpointURL = apiHostURL.appendingPathComponent(item.path)
        return URLRequest(url: endpointURL)
    }
}

The above is essentially the same behavior, but it is more verbose without being any safer or more readable. Even worse would be to try to make some recovery code instead of fatalError(). Any attempt at recovery in this block would almost surely be wrong, and would be more likely to mask the error and make it difficult to debug.

But the force unwrap operator is contrary to Swift’s philosophy of safety, I often hear. This misunderstands the specific meaning of safety that Swift strives for. Swift avoids unsafe memory access and undefined behavior. A crash from a force unwrap is not undefined: it is a controlled termination. It may not be an ideal user experience, but the program is not executing arbitrary or unknown commands.

Crashes in a language like C are often caused by undefined behavior, a prime example being dereferencing a null pointer. A modern OS will generally catch this and terminate the program. But the program may limp along in an undefined state before it terminates, possibly after corrupting data. This is what Swift helps you avoid, even when using the ! operator.