• Skip to main content
  • Skip to footer

Khawer Khaliq

  • Home

Unwrapping Optionals With Optional Binding in Swift

Share
Tweet
Share
Pin

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:

  • What Are Swift Optionals and How They Are Used
  • The Power of 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 = 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.

Subscribe to get notifications of new posts

No spam. Unsubscribe any time.

Reader Interactions

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Footer

Protocol-Oriented Programming (POP) in Swift

Use protocol-oriented programming to think about abstractions in a completely different way, leveraging retroactive modeling to introduce appropriate abstractions at any point in the development cycle, and creating traits that can let types opt into functionality simply by conforming to a protocol.

Pattern Matching With Optionals in Swift

The optional pattern explained in detail, including how to use it with a variety of conditional statements and loops, and how to add extra conditions when required, with a section on creating more complex pattern matching code involving optionals.

Test-Driven Development (TDD) in Swift

Learn how to use Test-Driven Development (TDD) in Swift which not only enables you to write more reliable and maintainable code but also allows refactoring of code in small increments and with greater ease and confidence.

Unit Testing and UI Testing in Swift

Learn how to use unit testing to gain confidence in the correctness of code at the unit level, and use UI testing to ensure that the application fulfills user requirements, explained in detail and illustrated using an example application built using SwiftUI.

When and How to Use the Equatable and Identifiable Protocols in Swift

Detailed coverage of the Equatable and Identifiable protocols, and how they can be used to model not only values but also domain entities with identity using Swift value types, to create code that is more efficient, easier to reason about, easily testable and more concurrency-friendly.

The Power of Optional Chaining in Swift

Learn how to use optional chaining to work safely with optionals, to set and retrieve the value of a property of the wrapped instance, set and retrieve a value from a subscript on the wrapped instance, and call a method on the wrapped instance, all without having to unwrap the optional.

What Are Swift Optionals and How They Are Used

This article explains what Swift optionals are, why Swift has them, how they are implemented, and how Swift optionals can be used to better model real-world domains and write safer and more expressive code.

A Protocol-Oriented Approach to Associated Types and Self Requirements in Swift

Use protocol-oriented programming to avoid having to use associated types in many situations but also to effectively use associated types and Self requirements, where appropriate, to leverage their benefits while avoiding the pitfalls.

Encapsulating Domain Data, Logic and Business Rules With Value Types in Swift

Leverage the power of Swift value types to manage domain complexity by creating rich domain-specific value types to encapsulate domain data, logic and business rules, keeping classes lean and focused on maintaining the identity of entities and managing state changes through their life cycles.

Rethinking Design Patterns in Swift – State Pattern

The State pattern, made simpler and more flexible with the power of Swift, with a detailed worked example to illustrate handling of new requirements, also looking at key design and implementation considerations and the benefits and practical applications of the pattern.

Conditional Logic With and Without Conditional Statements in Swift

Shows how to implement conditional logic using conditional statements as well as data structures, types and flow control mechanisms such as loops, balancing simplicity and clarity with flexibility and future-proofing.

Better Generic Types in Swift With the Numeric Protocol

Covers use of the Numeric protocol as a constraint on type parameters of generic types to ensure that certain type parameters can only be used with numeric types, using protocol composition to add relevant functionality as required.

Understanding, Preventing and Handling Errors in Swift

Examines the likely sources of errors in an application, some ways to prevent errors from occurring and how to implement error handling, using the error handling model built into Swift, covering the powerful tools, associated techniques and how to apply them in practice to build robust and resilient applications.

When and How to Use Value and Reference Types in Swift

Explores the semantic differences between value and reference types, some of the defining characteristics of values and key benefits of using value types in Swift, leading into a discussion on how value and reference types play a complementary role in modeling real-world domains and designing applications.

Swift Protocols Don’t Play Nice With Equatable. Or Can They? (Part Two)

Uses type erasure to implement Equatable conformance at the protocol level, allowing us to program to abstractions using protocol types while safely making equality comparisons and using functionality provided by the Swift Standard Library only available to types that conform to Equatable.

Copyright © Khawer Khaliq, 2017-25. All rights reserved.