Unwrapping Optionals With Optional Binding in Swift (2023)

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.

To learn more about optionals in Swift, please use the following links to explore why Swift has optionals, how optionals are implemented in Swift, common uses cases for optionals in Swift, and optional chaining in Swift.

Contents

What is optional binding
Multiple optional bindings
Optional binding with early exit
Combining optional bindings and boolean conditions
Optional binding with a loop
Name shadowing in optional binding
Optional binding vs. optional chaining
Using optional chaining with optional binding
Conclusion

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 = nilcheckForPossibleNamedCat(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 BellapossibleCat = Cat()checkForPossibleCatAndPossibleName(possibleCat)// Found a cat// The cat does not have a namepossibleCat = nilcheckForPossibleCatAndPossibleName(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 BellapossibleCat = Cat()checkForPossibleCatAndPossibleName2(possibleCat)// Found a cat// The cat does not have a namepossibleCat = nilcheckForPossibleCatAndPossibleName2(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.

Top Articles
Latest Posts
Article information

Author: Manual Maggio

Last Updated: 11/24/2022

Views: 6024

Rating: 4.9 / 5 (69 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Manual Maggio

Birthday: 1998-01-20

Address: 359 Kelvin Stream, Lake Eldonview, MT 33517-1242

Phone: +577037762465

Job: Product Hospitality Supervisor

Hobby: Gardening, Web surfing, Video gaming, Amateur radio, Flag Football, Reading, Table tennis

Introduction: My name is Manual Maggio, I am a thankful, tender, adventurous, delightful, fantastic, proud, graceful person who loves writing and wants to share my knowledge and understanding with you.