Optionals

Optionals are language defining feature of Swift–they are simply that powerful. Understandably, they can be hard to grasp and implement, but it is so worth it–you'll never have to deal with another NullPointerException or NoneType error again.

Importance of Nothingness

The problem we're trying to solve here is that sometimes we will have a representation for something and depending on the state, we will either want it to represent nothing and sometimes we will want it to represent something. This idea shows up in a bunch of different ways. For example, you may be doing some time-intensive task, so you want a variable to represent nothing until the task is done. Once the task is done, you want it to be the result of the task. You could also just want to represent nothingness. For example, you can have some data type that represents a person, and one of the fields is their favorite sandwich. Sometimes people just don't have favorite sandwiches, so you want it to be nothing–as for why, I don't know maybe they're indecisive or maybe they've just never had any sandwiches before, or maybe they think that hotdogs are sandwiches so their opinions are just invalid. Whatever the reason is, you may want it to be nothing.

Other Language's Solutions

Okay now that we're done with that excessively long explanation as for why we need the concept of nothingness, let's get into how it's implemented. In Python and Java, you can always just assign None or null to any variable. This is fine and fixes our problem, but the issue is that sometimes you will have a nothing value and will not be expecting it, so you don't handle it correctly. It is also notoriously annoying to debug because you may not be sure as to why something is nothing. OCaml uses a generic system that can either have no value None or an associated value Some <some value>. This is great it forces people to acknowledge the possibility of nothing and respond accordingly. This is very similar to Swift.

Optionals

In Swift, we leverage the compiler to handle nothing values for us. If a variable, arguments, or return type can be nothing (nil) the programmer must mark it so. Any type that has the potential to be nil is called an optional. We denote optional types the exact same way as you would for the original type, but with a question mark afterward. An optional Int is Int?–almost as if you're asking if it's actually there. Now to assign to an optional type we don't need any overhead or wrapping, as you would in OCaml, you just assign a value or nil.

var myFavoriteSandwich: String?
myFavoriteSandwich: String? = nil
// Notice how if we assign nothing, it starts out being nil
myFavoriteSandwich = "Southwest Sub"
myFavoriteSandwich : String? = "Southwest Sub"

myFavoriteSandwich = nil
myFavoriteSandwich: String? = nil

Optional Chaining

Creating optional variables and assigning optional values is nothing too new–you just add a question mark. But using optionals is where it becomes difficult in other languages, and the same is true same in Swift. We have two techniques to deal with optionals in Swift: optional chaining and unwrapping. When you want to access fields and methods of an optional type, we can use optional chaining. Optional chaining makes sure that an optional is not nil before calling the method or accessing a field. If the optional is nil, chaining will halt execution and return nil. We denote an optional chain with a postfix ?. We'll demonstrate a use case and why this is important below.

struct Sandwich {
    var ingredients: [String]? = nil
}

Above, we are representing making a sandwich. When we start, the sandwich has no ingredients, so the ingredients property is set to nil. This implementation forces us, whenever we try to access ingredients, to consider a nil case.

let mySandwich: Sandwich = ...
let myIngredients = mySandwich.ingredients
// myIngredients: [String]? = ...

Now, let's say that we want to get the number of ingredients in the sandwich. The thinking would be to do myIngredients.count, but this should hopefully scare some of you. How can we access a field or a method of a nil value? That property/method simply is not defined, so how do we deal with it? In other languages, you may get an error like "cannot access member of NoneType" or a NullPointerException–I don't know, it's been a while since I've used Python / Java. In Swift, we circumvent this with optional chaining.

let ingredientCount = myIngredients?.count
// ingredientCount: Int? = ...

Notice how the result of an optional chain is always optional.

You may be thinking "wow this is going to be super annoying to remember". And yes, you would be correct! Luckily, Xcode, in all of its majestic glory, will handle this for you! YAY! Sometimes, you will just be accessing a bunch of fields/methods and some magical question marks popping up. Don't worry! Just remember that Xcode is looking out for you!

You may also be thinking "cool, but sometimes I'd rather get out of optional land and get back onto a concrete footing". Great point! That's what unwrapping is for!

Unwrapping

There are times when you are working with optional values but will want a concrete result. If your process, no matter the state of your optional, is the same, you have two choices: force unwrapping and default values. Force unwrapping is what you do when you can guarantee that your optional variables are, in fact, not nil (at the time of access). To force unwrap, we use the ! or bang postfix operator: optionalVariable!. For example, because of the linearity of code, in the below example I can guarantee that myFavoriteSandwich is not nil, so I can force unwrap it.

var nehasFavoriteSandwich: String?
nehasFavoriteSandwich = "egg and cheese on biscuit from mcdonalds"
let IGuaranteeThisIsNotNil: String = nehasFavoriteSandwich!

There are many cases where it is not possible to guarantee there is a value in your optional. In this case, if we still need to convert to a concrete value, we will use a default value. We denote a default value with a double question mark and a default value optionalValue ?? defaultValue.

var favoriteSandwiches: [Sandwich]?
// If an array is nil you can often substitute in the empty list
var anotherListOfSandwiches: [Sandwich] = favoriteSandwiches ?? []

Conditional Unwrapping

Sometimes you may want significantly more complex behavior depending on the presence of a value or not. For example, if a value exists, you may want to execute code. Otherwise, you may just want to skip the rest of the code. For sake of explanation let's say we're working at a high-security facility. We will describe people who try to enter as shown below

struct Person {
    
    enum Position {
        case staff
        case securityGuard
        case president
    }
    
    let name: String
    let position: Position?
}

Every person has a name and an occupation. Well, if you're not hired you may have your occupation set to nil, but there's still something to describe your occupation. Let's say there is some door that will let people in if they are a security guard or a president. If we think about how this function will work, we are going to need two steps:

  1. Check if they have an occupation, if they do not, stop computation

  2. Now that we can guarantee they have an occupation, if they are a securityGuard or President then we let them in

This is a very common computation pattern, if a value does not exist -> stop execution, otherwise, continue. To do this type of control flow we do something called a guard-let statement. This statement has the syntax: guard let <insert identifier> = <insert optional> else { <some code that will end execution> }. Swift evaluates a guard-let statement by evaluating the optional, and if it does not exist, execute the escaping code (the else block). If there is a value in the optional, it stores value and binds it to the name in the let statement and is accessible for the rest of the code executed in that scope. I.e. if there is a value in the optional, it stores it in a variable for the rest of execution to use.

guard let <identifier> = <optional = o> else { <escaping code> }
<continuation code>

evaluate o to a value v
if v == nil { execute escaping code }
else { bind v to identifier -> execute continuation code }

Side note, in the case that you need to escape, you must produce a satisfying result. If you're in a function that has a return type, you must return something of that type. If it's void, you can just return. If you're in a loop, you can continue.

Okay, with guards under our belt, let's write our lock!

func door(person: Person) {
    guard let role = person.position else { return }
    if role == .securityGuard || role == .president {
        unlock()
    }
}

Now, let's imagine a second door. This door is uber-secure, so secure in fact, that if the person trying to open it does not have a role, or they are just staff, we want to detain them! Additionally, we want to take note of who tries to open the door, no matter what. You'll note that we can do this with guard let statements again–we just need to put some kind of detain call in the escaping branch. We'll get into the nuances in the next section–for now, bear with me.

For a control flow where we want to have different behavior depending on the state of an optional, and afterward, have shared behavior we use an if-let statement.

The syntax for if-let is very similar to guard let: if let <insert identifier> = <insert expression> { <insert code block> } else { <insert code block> }. Note that as this is an if statement, the else is optional. If the optional evaluates to a value, assign it to an identifier and execute a code block where the identifier is bound. Otherwise, execute some other code without access to the identifier.

func secureDoor(person: Person) {
    if let role = person.role {
        if role == .staff {
            detain()
        } else {
            unlock()
        }
    } else {
        detain()
    }
    print(person.name + " has tried to open the uber-secure door.")
}

You'll notice we have two paths that go to detain. This is fine, but we can optimize our code. With normal Swift conditionals, we could just combine the two with &&, but this does not work because a let statement is not a boolean. If we want to mix and match with if let and booleans, or if we want to stack if lets, we use a comma. This also works for guard statements.

func secureDoor(person: Person) {
    if let role = person.role, role == .safetyGuard || role == .president {
        unlock()
    } else {
        detain()
    }
    print(person.name + " has tried to open the uber-secure door.")
}

If you look closely, you might have noticed that I slightly lied–sorry. I previously said that if let and guard lets bind their value to the identifier within either the if branch or the rest of the code, respectively. But it binds at the time of evaluation. This allows us to take advantage of the order of our if statement. We evaluate the optional to a value and bind it to the identifier role. Then since the comma functions as an and, we evaluate boolean and, since role is bound, we substitute in role. Quite sleek.

How to Distinguish Between if-let and guard-let

For many of these situations, you can use either if-let or guard-let. In these cases, we refer to idioms or Swift best practices. Generally speaking, if the else branch is really short and escaping, use a guard-let statement. Otherwise, you if-let. Another way to think of it is to use guard-let when handling edge cases. But, if there is no code to execute after a guard-let statement, i.e. the branch to execute for a non-nil value is empty, you should follow the next section.

When Nil is the Priority

Sometimes, you will only want to execute some code if an optional is nil. So far, if this is what you want to do, you're going to have to do some absolutely disgusting code like:

if let _ = ... {} else {

}
// or
guard let _ = ... else {

}
// and this will not be executed when nil, so you're going to be duplicating things

So how do we fix this? Nothing new! Just use equality!

if ... == nil {
    ...
}

Implicitly-Unwrapping

There will be cases where you will not be able to initialize a field on the initialization of an object. But, by the time the field will ever be accessed, you will be able to guarantee that the field will be written to. You can handle this just by force unwrapping every time you access the field. This is fine, but why add extra work to your life? In this exact scenario, we can use something called implicit unwrapping–which functions exactly how it sounds. To do this, we force unwrap the type of a variable.

var myImplicitInteger: Int!
myImplicitInteger = 3
// myImplicitInteger: Int! = 3

let myRealInteger = myImplicitInteger + 3
// myRealInteger: Int = 6

I didn't have to do any optional chaining or unwrapping! Also, the product of my addition was not optional!

Last updated