In Swift 5.8, guard is a special form of an if
-statement—it ensures a condition is true. If the condition is not true, the guard must exit the current block.
A method may have invalid results if a parameter is not in the correct range. It is unsafe to use in this case. A guard can prevent logic errors later in the program.
This example introduces a printArea
func
. In printArea
we require both parameters to be greater than or equal to 1. Zero and negative values are invalid.
printArea
succeeds, as both arguments are valid. But the next two fail because of the guard clauses.func printArea(x: Int, y: Int) { // Validate that "x" argument is valid. guard x >= 1 else { print("X invalid") return } // Validate "y" argument. guard y >= 1 else { print("Y invalid") return } // Print area. let area = x * y print(area) } // Call printArea. printArea(x: 5, y: 10) // Use invalid "X" then invalid "Y" arguments. printArea(x: 0, y: 1) printArea(x: 2, y: 0)50 X invalid Y invalid
A guard is more restricted than an if
-statement. It is a special case of an if
-statement. A guard must exit (with return, break
) at the end of its list of statements.
func test(size: Int) { guard size >= 10 else { print(1) } print(2) }/.../main.swift:4:5: 'guard' body may not fall through, consider using 'return' or 'break' to exit the scope
The guard condition must have an else
-keyword. We can think of a guard as an if-else
with an empty "if" and a requirement that the control flow terminates in the "else."
func multiplySize(size: Int) -> Int { guard size >= 0 { return 0 } return size * 2 }/.../main.swift:2:21: Expected 'else' after 'guard' condition
Consider this program. We introduce printSum
, and this function has a guard statement. We bind the name "initial" to the result of the first property, which returns an optional.
Int
array exits, we continue with printSum
. Otherwise we return early.func printSum(values: [Int]) { // Use optional binding in guard. // ... Return if no first element. guard let initial = values.first else { print("No initial element") return } // Print first element. print("First: \(initial)") // Sum elements. var sum = 0 for element in values { sum += element } // Print the sum. print("Sum: \(sum)") } // Use printSum func. printSum(values: []) printSum(values: [10, 11])No initial element First: 10 Sum: 21
break
A guard can exit with a continue or break
statement. So we can use guard conditions in loops (just like if-else
statements).
while
-loop we reach a continue statement unless the loop variable is even. So we skip odd numbers.break
statement if the "i" variable is greater than 10. This terminates the loop with a guard condition.// Loop from 0 to infinity. var i = 0; while (true) { // Ensure we are on an even number. guard i % 2 == 0 else { i += 1 continue } // Ensure our number is less than or equal to 10. guard i <= 10 else { break } // Print our number. print("Number: \(i)") i += 1 }Number: 0 Number: 2 Number: 4 Number: 6 Number: 8 Number: 10
FatalError
A guard must exit its containing scope. With fatalError
we terminate the entire program. This counts as a valid exit condition.
fatalError
we can ensure this.var i = Int.max // This program must never be run with Int.max. guard i != Int.max else { // This is an exit condition like "return." fatalError("The Int.max cannot be used") } print("End")fatal error: The Int.max cannot be used: file /.../main.swift, line 6 Program ended with exit code: 9
A guard block must exit somehow. This can be done with a throw
-statement, which transfers control to the calling function. It enters an alternate control flow.
start()
func
throws an ErrorCode.CodeZero
unless its argument is 0. So we can only start()
with a zero argument.enum ErrorCode: Error { case CodeZero } func start(code: Int) throws { // This func must be called with argument 0 or it throws. // Use guard with "throw." guard code == 0 else { throw ErrorCode.CodeZero } // Begin. print("Start") } // Call start. try start(code: 900)fatal error: Error raised at top level: Test.ErrorCode.CodeZero... (lldb)
Why do we have guards in Swift? This is a construct that is meant to increase code readability. It makes code easier to validate and understand.
With a guard, we encode a branch that validates arguments or variables. It must return or break
. So we use guard to terminate on invalid state before further problems appear.