Newly introduced in Swift 2.0, the Swift guard
statement brings the power of the nightclub doorman to the Swift language and sits somewhere between the bulk-standard if
statement, and the more stringent and unforgiving assert
statement.
Along with the guard
statement comes the promise of cleaner, more explicit and a more elegant code and in this article I want to find out whether it lives up to these promises.
Table of Contents
The Example
In order to investigate, I’m going to use an (admittedly contrived) example.
Basically, it’s a function that accepts three optional Int
values and performs a simple calculation on those values if, and only if, all the values are both non-nil and positive. In all other cases the function will return nil.
In reality, the exact nature of the function isn’t important. What is, is the structure it provides and in this article, I’ll use it to investigate the evolution of the Swift language that has lead to the introduction of the guard
statement as well as looking at the guard
statement itself in terms of both syntax and usage. So on with the history lesson.
Swift 1.0
if-let-else
When Swift was originally released, working with optionals wasn’t ideal.
We had to check each optional value individually to ensure that they contained a value and only once all the checks were complete could we actually use those values in a calculation.
Optional binding helped, but it had its own problems.
For example, using optional binding, our example function at that time would have looked something like this:
func fooSwiftWhenFirstReleased(a : Int?, b: Int?, c:Int?) -> Int? {
if let a = a {
if let b = b {
if let c = c {
if a > 0 && b > 0 && c > 0 {
return a * b * c
}
} else {
return nil
}
} else {
return nil
}
} else {
return nil
}
}
Code like this was common.
The pattern of ever increasing nesting that optional binding introduced, led to the nickname The Swift Pyramid of Doom and as you can see, it wasn’t pretty.
It was not uncommon to go hunting through multiple levels of nested if
statement to decipher exactly what a function was doing and with all those braces and nested if
statements everywhere identifying the primary path could be particularly tricky.
On top of this, because one or more of the optional bindings could fail, we had to return an optional from the function. This placed additional responsibilities onto the person using the function to check whether the return value was nil
, further exacerbating the Pyramid of Doom problem.
Swift 1.2
Binding of Multiple Optionals on a Single Line
Swift 1.2 brought improvements though.
One of the major improvements was the ability to perform binding of multiple optionals on a single line.
In this case, Swift would evaluate each binding in turn and stop if any attempted binding was nil
:
func fooMultiBinding(a : Int?, b: Int?, c:Int?) -> Int? {
if let a = a, b = b, c = c {
if a > 0 && b > 0 && c > 0 {
return a * b * c
} else {
return nil
}
} else {
return nil
}
}
As you can see, this new capability all but eliminated the Swift Pyramid of Doom but still left the main flow through the function buried within a couple of levels of nested if
statement.
Swift 1.2 did however provide us with another card we could play.
The where
Clause
In Swift 1.2 we were also able to combine the binding of multiple optionals with an additional boolean where
clause within which we could place additional constraints on the values unwrapped in the binding.
With this where
clause then, not only did the optional bindings have to complete successfully, but the boolean expression after the where
had to evaluate to true
for the first branch of the if
statement to be executed:
func fooMultiBindingWithWhere(a : Int?, b: Int?, c:Int?) -> Int? {
if let a = a, b = b, c = c where a > 0 && b > 0 && c > 0 {
return a * b * c
} else {
return nil
}
}
Combining the optional binding with a where
clause saved us a level of nesting but still left the primary path through our code buried within a branch of the if
statement though.
Although Swift 1.2 was close then, we still weren’t quite there. With the newly introduced features of Swift 2.0 though, we might just have a solution.
Swift 2.0
The Swift guard
Statement
Swift 2.0, was released in June this year, and with it, came a new guard
statement that has gone along way to solve the issues that remained in Swift 1.2.
The guard
Statement Syntax
The general structure for the guard statement is as follows:
guard (conditionThatNeedsToBeTrue) else {
// return, break, continue or throw or a call to a method
// that doesn't return.
}
The guard
statement is similar to an if
statement in that the statements within the associated block are execute based on the results of a boolean expression. That boolean expression might contain normal variables or constants or may contain the results of optionally bound values in similar fashion to an if-let
statement. There are some subtle differences though.
Unlike the if
or if-let
statements, if the guard
statements conditions are met, execution continues after the closing brace of the guard statement and if the conditions are not met, the guard statements mandatory else
clause is executed.
It’s kind of the if
and if-let
statements in reverse. And the else
clause is no ordinary else
clause either.
In a guard
statement, Swift places an additional requirement on the else
clause that, if executed, it must exist the current scope using one of Swifts control transfer statements.
This can mean using either a return
to leave a function, a continue
or break
to leave a loop, a throw
to raise an exception or a call to a function that doesn’t return like fatalError()
(which will also crash your app). Either way, the else
clause must somehow exit the current scope.
You can think of a guard
statement then, as more like an assert
instead of an if
.
Instead of immediately crashing your application as an assert
would, the guard
statement mandates that we exist the current scope but gives us a chance to do so in a more graceful manner.
The guard
statement also works with optional binding too. Let’s re-write the example using the new guard
statement then.
func fooUsingGuard(a : Int?, b: Int?, c:Int?) -> Int? {
guard let a = a, b = b, c = c where a > 0 && b > 0 && c > 0 else {
return nil
}
return a * b * c
}
As you can see, the guard
statement allows us to simplify the code a little further.
It does so by changing the way we think about and deal with condition checking.
The Bouncer Pattern
One of the things the guard
statement does it so guide us towards checking values at the start of our function and exiting early if the values don’t meet any desired criteria.
This pattern of early check, early exit, is sometimes called the Bouncer Pattern and is based around the idea of checking values at the entry to the function (similar to a bouncer checking people as then enter a nightclub) and ejecting them if they don’t meet the desired criteria. Essentially it’s the code version of “If you’re names not down, you’re not coming in.”
Using the Bouncer Pattern helps keep the code that handles any broken conditions close to the condition check itself (rather than buried away in the else clause of an if
statement somewhere) but also makes the code cleaner by leaving us to focus on the primary path through our code in the latter parts of the function body.
On top of this, the guard
statement also has another major trick up its sleeve.
The guard
Statement and Optional Binding
When combined with optional binding, as with the normal if-let
syntax, one or more optional values are checked and automatically unwrapped. Now this is nothing new. You’ve seen optional binding before so you know how they work. But remember the point I mentioned earlier.
In the case of the guard
statement, the true
path is everything after the closing brace of the guard
statement so when used with optional binding, instead of the bound optional values being available inside the following block of code as they would be when bound using the if-let
syntax, they are instead available within rest of the function or block in which the guard
statement was called (in this case the top-level scope of the function). This allows us to make use of the unwrapped values at any point after the end of the guard
statement and allows us to do so without the need for any forced unwrapping.
The helps enhance the clarity of our code and keeps the primary flow through our function at, or at least close to, the top-level scope making it much easier to read and decipher. Never a bad thing.