This article covers optional binding, one of the mechanisms built into Swift to make optionals easier and safer to use. Optional binding conditionally unwraps an optional and extracts the wrapped value, if it exists, to a temporary constant or variable. Optional binding can be used to conditionally execute code, control the execution of a loop, and create early exit paths from the current scope. Multiple optional bindings can be used in one statement, and optional binding can be used in combination with optional chaining to make code more compact. The identifier used in optional binding can be new or it can shadow the name of the optional.
Related articles:
Contents
What is optional binding
Optional binding is a mechanism built into Swift to safely unwrap optionals. Since an optional may or may not contain a value, optional binding always has to be conditional. To enable this, conditional statements in Swift support optional binding, which checks if a wrapped value actually exists. If it does, the wrapped value gets extracted as a temporary constant or variable, which is available within the scope normally associated with the conditional statement.
An if
statement is the most common way to unwrap optionals through optional binding. We can do this by using the let
keyword immediately after the if
keyword, and following that with the name of the constant to which we want to assign the wrapped value extracted from the optional. Here is a simple example.
struct Cat {
var name: String?
var vaccinated = false
func makeSound() {
print("Meow")
}
}
var possibleCat: Cat? = Cat()
if let cat = possibleCat {
cat.makeSound()
}
// Meow
Since we have used the let
keyword, cat
is a constant so the following code does not compile.
if let cat = possibleCat {
cat.name = "Bella"
}
// Error: Cannot assign to property: 'cat' is a 'let' constant
This is because Cat
is a struct, which is a value type, and values in Swift are immutable. The only way to mutate a value is to create a new value and replace the existing value with the new one. In the above case, this requires a new value to be assigned to the cat
constant, which is not possible. Therefore, we get an error. This would not be an issue if we were dealing with an instance of a class since classes are reference types. A variable to which a class instance is assigned only holds a reference to the class instance, which is allocated on the heap. Let’s see this by defining a CatClass
type.
class CatClass {
var name: String?
init(name: String? = nil) {
self.name = name
}
}
When we have an optional where the wrapped type is CatClass
, and we assign an instance of CatClass
to this optional, what the optional contains is only a reference to the class instance. We can use optional binding to extract the wrapped value to a constant, and use the extracted value, which is a reference to the class instance, to mutate the class instance.
var possibleCatClass: CatClass? = CatClass()
if let catClass = possibleCatClass {
catClass.name = "Bella"
}
The above code works because we have not mutated the reference held by the catClass
constant but simply used it to mutate the CatClass
instance. We would get an error if we tried to give the catClass
constant a reference to a new CatClass
instance, as we see below.
if let catClass = possibleCatClass {
catClass = CatClass()
}
// Error: Cannot assign to value: 'catClass' is a 'let' constant
For situations where we need to use optional binding to extract the wrapped value to a variable, we can use var
instead of let
in the optional binding, as shown below.
if var cat = possibleCat {
cat.name = "Bella"
}
It is important to note that the identifier cat
, whether it is a constant or a variable, only exists within the first branch of the if
statement. As soon as this branch ends, the cat
identifier is no longer available.
if var cat = possibleCat {
cat.name = "Bella"
}
cat.name = "Whiskers"
// Error: Cannot find 'cat' in scope
Multiple optional bindings
To see multiple optional bindings in action, we assign to the possibleCat
variable we had defined in the previous section a new Cat
value, which is initialized with a value assigned to its name
property.
possibleCat = Cat(name: "Bella")
Since the name
property of Cat
is an optional, if we want to print the name of the cat we have initialized above, we have to use two optional bindings, first to extract the Cat
value, and then to extract its name. Here is how this can be done using nested optional bindings.
if let cat = possibleCat {
if let name = cat.name {
print(name)
}
}
// Bella
This does not read well, and if the purpose of the first optional binding is only to enable the second one, Swift allows us to include the two optional bindings in the same if
statement.
if let cat = possibleCat, let name = cat.name {
print(name)
}
// Bella
Note that both constants cat
and name
are available within the scope created by the above if
statement. The choice of whether to use nested optional bindings or combine multiple optional bindings in the same if
statements usually boils down to whether we are interested only in the final result produced by both the optional bindings or we also need to know if the first optional binding succeeded.
To illustrate the difference between the two approaches, consider the following function that uses two optional bindings in the same if
statement to check whether an optional contains a named cat.
func checkForPossibleNamedCat(_ possibleCat: Cat?) {
if let cat = possibleCat, let name = cat.name {
print("Found cat named \(name)")
} else {
print("Did not find named cat")
}
}
When we call this function with an optional wrapping a named cat, we get the result we expect.
possibleCat = Cat(name: "Bella")
checkForPossibleNamedCat(possibleCat)
// Found cat named Bella
However, things are not so clear when we call this function with an optional that wraps an unnamed cat.
possibleCat = Cat()
checkForPossibleNamedCat(possibleCat)
// Did not find named cat
This does not tell us whether a cat was not found at all or a cat was found but it did not have a name. As we can see below, we get the same message when we call this function with an optional that does not have a wrapped value at all.
possibleCat = nil
checkForPossibleNamedCat(possibleCat)
// Did not find named cat
Depending on the use case at hand, if this additional detail is of interest to us, we can use the following function that uses nested optional bindings to first check for the existence of a cat and, if a cat is found, check whether the cat has a name.
func checkForPossibleCatAndPossibleName(_ possibleCat: Cat?) {
if let cat = possibleCat {
print("Found a cat")
if let name = cat.name {
print("The cat is named \(name)")
} else {
print("The cat does not have a name")
}
} else {
print("Did not find a cat")
}
}
The code above is a bit confusing, as code with nested if-else
statements often tends to be, but it gets the job done since we get feedback as each optional is unwrapped. We will see a better way to achieve the same result in the following section but for now we see the output we obtain from this function for the same three cases that we had tested with the function presented earlier.
possibleCat = Cat(name: "Bella")
checkForPossibleCatAndPossibleName(possibleCat)
// Found a cat
// The cat is named Bella
possibleCat = Cat()
checkForPossibleCatAndPossibleName(possibleCat)
// Found a cat
// The cat does not have a name
possibleCat = nil
checkForPossibleCatAndPossibleName(possibleCat)
// Did not find a cat
As noted earlier, whether we use separate optional bindings or combine the optional bindings in a single if
statement depends on whether we are interested only in the end result or we also need to get information or take action depending on the result of one or more intermediate optional bindings.
Optional binding with early exit
In situations where we need to take action both when an optional binding succeeds and when it fails, and we are dealing with multiple optionals, we can get messy and unclear code with nested if-else
statements, as we saw in the previous section. The preferred alternative usually is to employ guard
statements to enable early exit paths if certain conditions are not met. This can be done with optional bindings to make code such as the checkForPossibleCatAndPossibleName(_:)
function easier to read. Shown below is another function checkForPossibleCatAndPossibleName2(_:)
, which achieves the same result by using using guard
clauses with optional binding.
func checkForPossibleCatAndPossibleName2(_ possibleCat: Cat?) {
guard let cat = possibleCat else {
print("Did not find a cat")
return
}
print("Found a cat")
guard let name = cat.name else {
print("The cat does not have a name")
return
}
print("The cat is named \(name)")
}
The logic of the above function is much easier to follow. First, it gets rid of the multiple levels of indentation. Second, the flow of decisions being made, and actions taken as a result, is clearer and more readable. The results, as shown below, are exactly the same.
possibleCat = Cat(name: "Bella")
checkForPossibleCatAndPossibleName2(possibleCat)
// Found a cat
// The cat is named Bella
possibleCat = Cat()
checkForPossibleCatAndPossibleName2(possibleCat)
// Found a cat
// The cat does not have a name
possibleCat = nil
checkForPossibleCatAndPossibleName2(possibleCat)
// Did not find a cat
As with using optional binding with an if
statement, if we want to modify the value extracted from an optional through optional binding used with a guard
statement, we can use guard var
instead of guard let
. In fact, using var
with a guard
statement is usually more useful since the variable to which a wrapped value is extracted with an if
statement is available only for the first branch of the if
statement whereas the variable to which a wrapped value is extracted with a guard
statement is available for the entire scope enclosing the guard
statement.
Combining optional bindings and boolean conditions
Optional bindings can be combined with boolean conditions in any order. For an if
statement with multiple optional bindings and multiple boolean conditions, the first branch of the if
statement will be executed only if all of the optional bindings succeed and all of the boolean conditions evaluate to true. If any of the optional bindings does not succeed or any of the boolean conditions evaluates to false, all subsequent optional bindings and boolean conditions are ignored, the first branch of the if
statement is not executed, and any statements in an else
clause, if present, are executed.
Let’s consider the following function that uses an if
statement with two optional bindings that extract a Cat
value and its name, and a boolean condition that tests whether the extracted name matches a given name.
func checkPossibleCat(_ possibleCat: Cat?, forName nameToMatch: String) {
if let cat = possibleCat, let name = cat.name, name == nameToMatch {
print("Found a cat named \(nameToMatch)")
} else {
print("Did not find a cat named \(nameToMatch)")
}
}
We first test this function with an optional wrapping a cat with the name we are looking for.
possibleCat = Cat(name: "Bella")
checkPossibleCat(possibleCat, forName: "Bella")
// Found a cat named Bella
Next we use an optional cat with a different name. In this case, both the optional bindings succeed but the boolean condition evaluates to false so the execution transfers to the else
clause.
possibleCat = Cat(name: "Whiskers")
checkPossibleCat(possibleCat, forName: "Bella")
// Did not find a cat named Bella
Finally, we provide a cat without a name, which produces the same result since the second optional binding fails, which transfers execution to the else
clause without evaluating the boolean condition.
possibleCat = Cat()
checkPossibleCat(possibleCat, forName: "Bella")
// Did not find a cat named Bella
Optional binding with a loop
Optional binding can also be used with while
loops using an approach similar to what we have seen with the if
statement. The let
keyword is used after the while
keyword and the statements in the while
loop execute as long as the optional binding continues to succeed. As with an if
statement, any number of optional bindings and boolean conditions, in any order, can be used to control the execution of a while
loop.
The function below uses a while
loop with an optional binding and a boolean condition to check whether all the elements in an array of optional cats are cats that have been vaccinated. If an element is found to not contain a cat, or to contain cat that has not been vaccinated, the loop is terminated. If not, the loop continues until all elements have been unwrapped and checked.
func checkAllVaccinatedCatsInArray(_ array: [Cat?]) {
var index = 0
while let cat = array[index], cat.vaccinated {
index += 1
if index == array.count {
print("All vaccinated cats found")
return
}
}
print("Did not find vaccinated cat at index \(index)")
}
Here is what this function does when given an array of optionals wrapping Cat
values, all of which have true
as the value of the vaccinated
property.
var possibleCats: [Cat?] = Array(repeating: Cat(vaccinated: true), count: 3)
checkAllVaccinatedCatsInArray(possibleCats)
// All vaccinated cats found
Next, we append to the array an optional wrapping a Cat
value that has the default false
value for its vaccinated
property, and call the function again.
possibleCats.append(Cat())
checkAllVaccinatedCatsInArray(possibleCats)
// Did not find vaccinated cat at index 3
Name shadowing in optional binding
In all the examples shown so far in this article, we have made it a point to use the prefix possible
in the names of optionals. This is why we were able to use distinct names for the constants and variables to which the wrapped values were extracted through optional binding. In real applications, however, optionals tend to have names that can also be given to normal variables, such as cat
, person
, employee
, etc. This can cause issues if a distinct name is desired for the constant or variable used with optional binding, forcing programmers to think up unnatural-sounding names that use prefixes such as unwrapped
and nonOptional
to avoid a conflict with the name of the optional, as shown below.
var cat: Cat? = Cat()
if let nonOptionalCat = cat {
nonOptionalCat.makeSound()
}
// Meow
This is not required, however, as Swift allows shadowing of the name of the optional, which means we can use a constant or variable with optional binding which has exactly the same name as the optional. We can rewrite the above code as follows.
if let cat = cat {
cat.makeSound()
}
// Meow
In the above code, within the first branch of the if
statement, the optional variable cat
is shadowed (in effect hidden) by the non-optional constant cat
to which the wrapped value is extracted. Aside from purely stylistic considerations, the main point to consider when deciding which syntax to use to unwrap an optional is whether there is a suitable name available to be used for optional binding that is different from the name of the optional. Unless such a suitable name is available, which would make the code clearer and more understandable, it is preferable to use the shadowing approach.
The shadowing syntax shown above may be a bit confusing at first since it does not seem to differentiate between the optional being unwrapped and the temporary constant or variable to which the wrapped value will be extracted. It can also get a bit cumbersome if the optional has a long name. A Swift evolution proposal has been accepted for inclusion in Swift 5.7, which will remove the boilerplate currently required. With the new syntax, rather than writing if let cat = cat
, we will simply be able to write if let cat
. This syntactic sugar is expected to make optional binding with name shadowing more compact and clearer by removing the unnecessary, and potentially confusing, repetition of the same name.
Optional binding vs. optional chaining
Optional chaining provides a convenient and compact mechanism to send messages to a value wrapped by an optional, including setting and getting the value of a property, and invoking a method. If there is a wrapped value, the intended operation succeeds; if not, the optional chain fails gracefully without causing an error or crash. Optional chaining provides an alternative way to call the makeSound()
method on a Cat
value wrapped by an optional, as shown below.
possibleCat = Cat()
possibleCat?.makeSound()
// Meow
Similarly, we can use an optional chain to set the value of the name
property of a Cat
value wrapped by an optional.
possibleCat = Cat(name: "Bella")
possibleCat?.name = "Whiskers"
The question that naturally arises is when is it advisable to use optional binding or optional chaining. The first consideration is whether we are using the optional to send a single message to the wrapped value or do we need to use the wrapped value for multiple operations. For a single operation, an optional chain is likely to be preferable, as in the above examples. However, if we need the wrapped value for multiple operations, it may be better and clearer to extract it using optional binding.
We also need to consider what kind of operations we need to perform. For operations that seek to mutate the wrapped value, such as setting the value of a property, or calling a mutating method, an optional chain will get us the desired result in a single operation because optional chaining acts directly on the wrapped value. Optional binding, on the other hand, creates a copy of the wrapped value so we have to mutate this copy, and then replace the value wrapped by the optional with the new value to achieve the same effect. We just saw how we can use an optional chain to assign a value to the name
property of the Cat
value wrapped by the possibleCat
optional. Here is how the same result would be achieved using optional binding.
possibleCat = Cat(name: "Bella")
if var cat = possibleCat {
cat.name = "Whiskers"
possibleCat = cat
}
It is important to note that this two-step process is required when the wrapped type is a value type, such as a struct. For value types, the optional wraps the actual value, and optional binding copies this value to the temporary constant or variable, as we have done with the cat
variable above. We, therefore, have to assign the new value back to the possibleCat
optional.
With a class, which is a reference type, the value wrapped by the optional is just a reference to the class instance. Optional binding creates a copy of this reference, which we can use to mutate the class instance. Because the optional still contains a reference to the same class instance, the change automatically reflects in the optional, as shown below (also note that we can use if let
since we are not mutating the reference extracted to catClass
).
possibleCatClass = CatClass(name: "Bella")
if let catClass = possibleCatClass {
catClass.name = "Whiskers"
}
Finally, we need to consider whether the wrapped value will be used in a context where an optional cannot, or should not, be used. For such cases, optional binding is the only way since it unwraps the optional and extracts the wrapped value whereas an optional chain simply reaches into the optional to work with the wrapped value without unwrapping it. Let’s say we want to print the value wrapped by the name
property of a Cat
value wrapped by the possibleCat
optional. If we try this using an optional chain, we do not get the intended output, and we get a warning, because the print(_:)
function expects a non-optional argument.
possibleCat = Cat(name: "Bella")
print(possibleCat?.name)
// Optional("Bella")
// Warning: Expression implicitly coerced from 'String?' to 'Any'
In the above case, a workaround is to use the nil-coalescing operator ??
, which unwraps an optional if it has a wrapped value, and provides a default value in case there is no wrapped value, as also shown below.
possibleCat = Cat(name: "Bella")
print(possibleCat?.name ?? "")
// Bella
While this may work in some cases, using the nil-coalescing operator requires a default value to be specified, which may not be possible in many scenarios. There are other instances where optional chaining simply will not work and we have to use optional binding. This would be the case, for instance, if we have an optional wrapping a Cat
value and we have to call a function with a non-optional parameter of the Cat
type, such as the vaccinate(_:)
function below.
func vaccinate(_ cat: inout Cat) {
cat.vaccinated = true
}
To call this method, we have to unwrap the optional using optional binding, as shown below.
possibleCat = Cat()
if var cat = possibleCat {
vaccinate(&cat)
}
Using optional chaining with optional binding
While at times we may find ourselves having to choose between optional chaining and optional binding, more often they are complementary techniques that can be used together. As we had seen in an earlier section, if we want to print the value wrapped by the name
property of a Cat
value wrapped by an optional, we can use two optional bindings in the same if
statement, as shown below.
possibleCat = Cat(name: "Bella")
if let cat = possibleCat, let name = cat.name {
print(name)
}
// Bella
Both the cat
and name
constants are available in the scope created by the above if
statement. If, however, all we need to do is to print the name of the cat, we don’t need the cat
constant, which we are creating only to support the subsequent optional binding. This not only makes code long-winded, it also forces us to create extra identifiers, like cat
in the above example, causing unnecessary distraction. A better solution for the above use case is to use optional chaining in combination with optional binding. We can use an optional chain to reach into the possibleCat
optional, without having to unwrap it, which enables us to use optional binding to directly unwrap the optional name
property, as shown below.
possibleCat = Cat(name: "Bella")
if let name = possibleCat?.name {
print(name)
}
// Bella
Conclusion
Optionals are a key part of Swift, and optional binding is one of the mechanisms built into the language to make optionals easier and safer to use. We can use optional binding with an if
or guard
statement, or in a while
loop, and combine multiple optional bindings and multiple boolean expressions in a single statement. Swift allows name shadowing with optional binding, which eliminates the need to create new identifiers when unwrapping optionals. In Swift 5.7, this will be possible using a more compact and readable syntax. Optional binding can also be used in conjunction with optional chaining to avoid having to unnecessarily unwrap optionals.
Thank you for reading! I always appreciate constructive comments and feedback. Please feel free to leave a comment in the comments section of this post or start a conversation on Twitter.
Leave a Reply