OR: How I Learned to Stop Worrying and Love the BANG!
(With apologies to Doctor Strangelove.)
What am I talking about?
There are four ways of doing force-unwrapping or similar operations in Swift, all of which could lead to your code crashing— and that’s not a bad thing!
1: Force-Unwrapping
let x: Int? // `x` is an Optional<Int>
// ...
// Stuff happens here. `x` could be never be assigned and still be `nil`.
// ...
let y = x! // We're force-unwrapping `x` -- could crash if `x` is `nil`!
2: Implicitly-Unwrapped Optionals
let x: Int! = nil // `x` is an implicitly-unwrapped Optional<Int>
// ...
// Stuff happens here. `x` could be assigned a value or may still be `nil`.
// ...
let y: Int = x // We're force-unwrapping `x` implicitly -- could crash if `x` is `nil`!
3: Force-Downcasting
class A { }
class B: A { }
var a: A = B()
// ...
// Stuff happens here. `a` could be reassigned to an instance of another subclass of `A` not inheriting from `B`.
// ...
let b = a as! B // Force-downcasting `a` to a `B` -- could crash if `a` does not inherit from `B`!
4: Force-Casting to a Protocol
protocol P { }
class C { } // `C` does not conform to `P`
class D: C, P { } // `D` inherits from `C` and also conforms to `P`.
var c: C = D()
// ...
// Stuff happens here. `c` could be reassigned to an instance of another subclass of `C` not conforming to `P`.
// ...
let p: P = c as! P // We are casting `c` to `P` -- could crash if `c` does not conform to `P`.
As software developers, we know that anything that could make our code crash is a bad thing. And that is generally true. However, it is specifically false. What I mean is that, while in general you never want your deployed code to crash in the field…
- If there are flaws in your code you want to find out as early as possible, in such a way that it is as obvious as possible where the problem lies.
- If it does crash in the field you want to find out why, so you can fix it.
The solution to this, believe it or not, is controlled crashing. And this is a feature of Swift, not a flaw. As in the examples above some kinds of (but not all) controlled crashes in Swift happen when:
- Attempting to force-unwrap an
Optional
that isnil
, - Attempting to read an
Optional
value that has been declared implicitly unwrapped and happens to benil
, - Attempting to downcast a class instance to a subclass that it does not inherit from at runtime,
- Attempting to cast an instance to a protocol that it does not conform to.
All of these possible types of runtime crashes involve the use of the Swift force-unwrapping operator !
or the keywords that incorporate an exclamation point such as as!
or try!
. Collectively I refer to these as the BANG!. The BANG! is used specifically to mark operations that, in theory could cause a runtime crash.
⚠️ Note: What I am calling the BANG! here should not be confused with the logical-negation operator !
, which appears before a boolean expression. Nor should you assume that the BANG! are the only ways of causing controlled crashes in Swift: two additional examples not of interest here include integer overflow and array index out of bounds errors.
So is it any wonder that the BANG! has a bad reputation among some? But why is that? I have found that among many programmers who come from a C/C++ background, force-unwrapping carries the unfortunate stigma that it is somehow like dereferencing a NULL
pointer in C/C++. This should never be done. However, this is because in the C and C++ language specifications, the result of dereferencing NULL
is undefined, which means the code may crash, or may not, or other bad things may (sometimes) happen. It is the unpredictability of the situation that makes it truly dangerous.
In Swift on the other hand, unwrapping nil
, or downcasting to the wrong type, is clearly-defined behavior— it causes an immediate abnormal termination of the process, i.e., a crash. In fact, this is one of the design goals of Swift: to remove as many causes of undefined behavior as possible. The preconditionFailure()
function in the Swift standard library also causes a controlled crash. Uses of the BANG!, like preconditionFailure()
, are used to mark places in the code where the programmer literally asserts “this should never happen.” The use of force-unwrapping, implicitly-unwrapped values, and forced-downcasting should be thought of as a kind of precondition enforcement and used accordingly.
What does this mean? A programmer who uses the BANG! is saying, “At this point in the code, this value will never be nil
, or will always downcast to the expected type.” Why is this important? Several reasons. For one, any time you have to deal with optionals, you have to deal with the possibility that the value will be nil
. This creates an alternate code path and you have to decide what to do with it. If you’re not going to crash, what are you going to do? Throw an exception? Who will handle it? Log the error to the console? Who will ever see it? Silently fail and do nothing? What if the caller always expects correct behavior? Then you’ve just swept a bug under the rug.
The fact is, once a precondition has been violated, all bets are off. You already lost the game. The question is, are you going to face up to it by making it painfully obvious you have a problem, or are you going stick your head in the sand and pretend it didn’t happen? Letting your app crash is facing the facts: you (or someone on your team) made a mistake and now you’ve got to fix it. And it is also the best way to learn about mistakes you’ve made in deployed code. While it’s never fun to have your app crash on a user, symbolicated crash dumps are a wonderful thing: they show you the exact line of code where you tried to unwrap nil
or downcast an instance to the wrong type. They give you everything you need to fix your mistake. On the other hand, ignored preconditions and silent failures lead to apps that just behave in crappy ways without actually crashing— empty views, unresponsive UI, and worst of all: bad App Store ratings and reviews— and it won’t be obvious to you why everyone hates your app.
Avoiding The BANG!
Now that I’ve sung the praises of letting things go BANG!, let me moderate that. Forcing things is only a tool, and the trick to becoming a master at anything is knowing which tools to use in which situations. Alternatives to the BANG! should always be considered, and the best way to avoid the BANG! is to avoid code paths where it would otherwise be required.
Avoid Non-Optional Optional Arguments and Attributes
Allowing a client to pass nil
for an argument value implies that the function can engage in correct, fully-functional behavior when that argument is nil
. In other words, only make arguments Optional
when they are really… optional. If your function cannot do it’s job when that argument is nil
, you simply shouldn’t allow the API for that function to accept nil
for that argument. In the following example, adding 10
to x
is only possible if an actual value is provided, and therefore only the last implementation should be seriously considered:
// Will crash if `x` is `nil`:
func addTen(to x: Int?) -> Int {
return x! + 10
}
// Will silently fail if `x` is `nil`:
func addTen(to x: Int?) -> Int {
guard let x = x else { return x }
return x + 10
}
// Will log an error if `x` is `nil`:
func addTen(to x: Int?) -> Int {
guard let x = x else {
print("x is nil!")
return x
}
return x + 10
}
// Will return `nil` if `x` is `nil`:
func addTen(to x: Int?) -> Int? {
guard let x = x else { return nil }
return x + 10
}
// Can never fail (except in case of `Int` overflow)
func addTen(to x: Int) -> Int {
return x + 10
}
But what if x
can be nil
? The simple fact is, the job of a function like addTen()
is simply to add ten, and the client of such a function should never call it unless it has a value to add ten to. Situations like not having a value to work with should be handled at the highest level of code possible. A low level function with a simple job to do should just be given what it needs to do its job every time without fail.
Similarly, stored properties in types you define (struct
or class
) should only be declared optional when having nil
for the property is truly part of normal operation of that type.
Use Uninitialized Values
When working with local variables or constants (declared with var
or let
) the compiler will ensure that values are initialized before being read. The following code works, but needlessly declares tile
as a force-unwrapped optional. Since randomTile()
always returns a Tile
, then tile
will always have a value at the end of the repeat loop.
class Board {
func randomEmptyTile() -> Tile {
var tile: Tile! // The BANG! is unnecessary
repeat { tile = randomTile() } while !tile.isEmpty
return tile
}
func randomTile() -> Tile {
// ...
}
}
Use guard let
and if let
Swift provides guard let
and if let
chief among the runtime alternatives to force unwrapping. If you use these constructs, you should always have in mind how to handle the case where the test fails, and the result should still be considered normal functionality, and it should never result in a silent failure or crash.
// Takes an optional `Double`, but will crash if it is `nil`
func stringForTemp(_ temp: Double?) -> String {
let tempInt = Int(temp!.rounded())
return "\(tempInt)°"
}
// Takes an optional `Double`, but always returns a `String`
func stringForTemp(_ temp: Double?) -> String {
guard let temp = temp else { return "--°" }
let tempInt = Int(temp.rounded())
return "\(tempInt)°"
}
Avoid Using Guard Values
Over-use of techniques to avoid using the BANG! result in overly-verbose code with a multiplicity of code paths to deal with the nil
case that will never be taken. It also encourages bad coding such as using guard values:
// Incorrect if "" is a value that needs to be checked for. Use an `Optional` instead.
var s1: String = ""
// Correct if dealing with the `nil` case is normal and needs to be routinely handled.
var s2: String?
// Correct if dealing with the `nil` case is abnormal and should never happen,
// but initial assignment must happen at a later point in the code.
var s3: String!
Embracing the BANG!
Like other preconditions, the point of the BANG! is to make the code fail fast. Just because the BANG! cause a crash doesn’t mean it’s bad— it’s actually very good when used correctly, and there are many cases where it can and should be employed. Unfortunately I have seen engineers who presume they must at all costs avoid ever using the BANG!. And otherwise great tools like SwiftLint by default flag all uses of the BANG! as a stylistic violation and contribute to the “all force-unwrapping is bad” mentality that ultimately causes bad code to be written— code with a multiplicity of paths that makes bugs more subtle and hard to track down. Here are some examples of places where it’s good to use the BANG!:
Late-Bound Values
In Swift, we prefer to use constants (declared using let
) over variables (declared using var
) as much as possible. The place to initialize an instance’s constant attributes is in the constructor (init()
). However, sometimes values that are effectively constant need to be set at a time after the constructor runs. This means they can’t be let
constants, they must be var
. If they are variables, but are always expected to have values, and never allowed to be nil
, this is a case for force-unwrapped optionals. One such common case is Interface Builder outlets:
class MyViewController: UIViewController {
@IBOutlet weak var myButton: UIButton!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated: animated)
myButton.addTarget( /* ... */ )
}
}
Remember that force-unwrapped optionals are just like other optionals— they can still be tested for nil
without causing a crash. They simply cannot be read like a non-optional value unless they have a non-nil value.
In this example a game Piece
is initialized with its orientation and energy level. The orientation
does not change throughout the life of the piece so it is declared as let
, but the piece can gain or lose energy
, so it is declared as var
. The piece can move from to any Tile
on the board, but which tile it will first occupy is not known at construction time, so it cannot be assigned in the constructor. But when a piece is in play it can be assumed to always occupy a tile, so tile
is properly declared as a force-unwrapped optional. The didSet
observer on the tile
variable triggers a call to the syncToTile()
function, which can assume that tile is always non-nil by simply using the tile
identifier without having to explicitly unwrap if to check for the nil
case. Simply reading the code you can determine that tile should never be set to nil
and that to do so is a programmer error.
class Piece {
var tile: Tile! {
didSet { syncToTile() }
}
let orientation: Orientation
var energy: Double
init(orientation: Orientation, energy: Double) {
self.orientation = orientation
self.energy = energy
}
private func syncToTile() {
// ...
}
}
When You Can’t Avoid Coding Defensively
Mostly what I am trying to teach here is: code only as defensively as you have to. Code that is over-defensive is error prone and hard to understand and maintain. Failing to use the BANG! where appropriate is a common cause of over-defensive code.
Generally speaking, API documentation should be considered a contract between clients and servers, and if the server fails to live up to it’s end of the bargain, the client is justified in engaging in undefined behavior, typically a crash. However, this only applies to cases where the client and the server are controlled by the same organization. If the app you’re writing is depending on an API created by you or your organization, you should be able to trust it and therefore if the API docs say a field is always present (for example, in a JSON dictionary) you should be able to code as if it will always be there.
func decodeID(from dict: [String: Any]) -> Int {
return dict["id"] as! Int // Force cast will crash if "id" is `nil` or is not a number.
}
When you are working with untrusted data, which includes data entered by users or data provided by untrusted third-party APIs, you should always code defensively:
func decodeID(from dict: [String: Any]) throws -> Int {
guard let id = dict["id"] as? Int else {
throw FormatError("missing record id")
}
return id
}
Note that the implementation above uses exceptions to delegate what to do about the error to its caller. This function has a simple job to do and if it can’t do it, it doesn’t silently fail nor quietly log an error— however, deciding what to do about the problem is “above this function’s pay grade.”
Example: Six BANG!s and NO crashes
Below is a code example extracted from an app I am currently working on. It involves being able to share contact information via e-mail or text message. class PresentViewController
shows the information to be shared and class ShareViewController
collects where to share it to, depending on the contact method selected by the user. Note that in every case where the BANG! is used (including preconditionFailure()
) it is a programmer error if it ever crashes. To use non-BANG! techniques in any of these cases would make the code more complex without adding any real safety.
enum ContactMethod {
case email
case message
}
class PresentViewController: UIViewController {
private var selectedContactMethod: ContactMethod! // BANG!
//
// This asserts it will always be set to not-`nil` before read.
// It is necessary because the contact method must be saved between
// the time the user selects the contact method and the time
// the target view controller is instantiated.
//
// **COULD** crash if read while `nil`, but
// **WON'T** crash because always set before prepare(for:sender:) is called.
// If it **DOES** crash it is a programmer error.
//
//
// ...
//
private func onEmailButtonTapped() {
presentShareViewController(with contactMethod: .email)
}
private func presentShareViewController(with contactMethod: ContactMethod) {
selectedContactMethod = contactMethod
performSegue(withIdentifier: "toShare", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.identifier! { // BANG!
//
// This asserts that `segue.identifier` will never be `nil` here.
// It is necessary because we only want to test for non-`nil` values
// and if we don't force-unwrap it here then we will have to test
// for them explicitly in the `case` clauses below.
//
// **COULD** crash if it is ever `nil`, but
// **WON'T** crash because in all storyboard segues MUST have identifiers.
// If it **DOES** crash it is a programmer error.
//
case "toShare":
//
// This is a force-downcast of an undifferentiated UIViewController instance
// to the expected ShareViewController instance. It is necessary because
// we need to set the `contactMethod` on the destination view controller
// so it can do its job.
//
// **COULD** crash if the destination view controller is not `ShareViewController`
// or a subclass thereof.
// **WON'T** crash because the programmer keeps the class declared in the
// storyboard consistent with this code.
// If it **DOES** crash it is a programmer error.
//
let viewController = segue.destination as! ShareViewController // BANG!
viewController.contactMethod = selectedContactMethod
case "toGallery":
//
// ...
//
default:
preconditionFailure() // BANG!
//
// This asserts that the only valid identifiers are the ones in
// the `case` clauses above. It is necessary because there is no
// reasonable way to continue if we receive an unknown identifier.
//
// **COULD** crash if `segue.identifier` ever has any other value, but
// **WON'T** crash because the only identifiers in the
// storyboard should be the ones listed here.
// If it **DOES** crash it is a programmer error.
//
}
}
}
class ShareViewController: UIViewController {
var contactMethod: ContactMethod! // BANG!
// This asserts that to do it's job properly, this view controller
// MUST have this variable set. It is necessary because the contact
// method cannot be known at construction time but MUST be known before
// the view controller is presented.
//
// **COULD** crash if this view controller attempts to appear without
// it being set.
// **WON'T** crash because client code understands the contract.
// If it **DOES** crash it is a programmer error.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// This is an implicit force-unwrap of `contactMethod`.
// It asserts that we assume that at this point in the code, `contactMethod`
// must always have been set to a non-`nil` value, and that there is no
// reasonable way to continue if it is `nil`.
//
// **COULD** crash if this code runs with `contactMethod` being `nil`,
// **WON'T** crash because client code understands the contract.
// If it **DOES** crash it is a programmer error.
if contactMethod == .email { // BANG!
//
// ...
//
}
}
}
Remember: The BANG! is a tool, and like any tool there are right and wrong times to use it.
🐺