What are Optionals in Swift?

Published on: August 12, 2024

In an earlier article, I explained how variables are defined in Swift using let and var. Both constants (let) and variables (var) in Swift always have a type; it's what makes Swift a strongly typed language.

For example, we could define a String variable like this:

// the compiler will know myString is a String
var myString = "Hello, world"

// we're explicitly telling the compiler that myString2 is a String
var myString2: String = "Hello, world"

This way of defining variables makes a lot of sense when it's possible to immediately assign a value to our variable.

However, sometimes you'll write code where it's not possible to assign a value to your variable immediately. Or you're working with functions that may or may not be able to return a valid value.

In Swift, we call values that can distiguish betwene having a value and not having a value an Optional. Before we dig too deeply into how we work with optionals, let's explore the difference between "no value" and "default" value so that we understand exactly why optionals exist in the first place.

If you prefer to learn through video instead of text, check out this video on my YouTube channel

The difference between a default value and no value

In programming, working with a concept called null or as Swift calls it nil will generally mean that a variable or a function's return value can be "nothing". There's a lot of technical baggage surrounding the terminology but in order to establish a good working knowledge, we won't dig into that too deeply.

The important thing to understand here is that defining an empty String like this: var myString = "" defines a String with a default value. The value is empty but the var myString is holding on to some data that will represent an empty String. Often this is a perfectly fine choice.

Now let's consider a different example where a default value would be a lot harder to define.

var theUser: User = ???

Our User object can't be created without input from other sources. And this input might not be present at that time that we define our variable. We'll need a way to define this var theUser with no data rather than a default value.

A real world analogy you might think of is the following. When you sit down at a cafe for some drinks, you will initially have no glasses or cups on your table. As a result, your waiter will know that you haven't been served anything at all so they'll know to go over and hand you a menu, introduce themselves and see whether they can take any orders. Once you've been served you might have some empty glasses on your table. The waiter will now know to ask to refill or take a different order.

This is a demonstration of how no value (no glass on the table) and an empty value (empty glasses on the table) can have significant differences in meaning and they can't always be used interchangeably.

In Swift, we express the ability of a property having no value rather than a default value by defining an optional User object:

var theUser: User?

The ? after our User tells the compiler that var theUser will either contain a value of type User or it will hold nothing at all (we call this nil).

It's nice to know that the ? is a more convenient to write the following:

var theUser: Optional<User>

While the two ways of defining theUser do the same thing, it's best practice to write var theUser: User?. It's easier to read and faster to write.

Note that all types in Swift can be written as an optional. For example, if you're defining a String that might need to be initialized as "no value" you could write: var theString: String?.

The main difference between "no value" and "default value" is often whether there's any semantic meaning to pointing at nothing or pointing to a default value. For example, an optional Bool (Bool?) almost never makes sense; in most scenarios you will be able to pick a sensible default value that's safe to use. In other cases, something being empty or missing could indicate that input from the user is required, or that you need to fetch data from an external source and it's not possible or reasonable to provide a default value.

Now that you know how to write optional properties, let's see how optionals are used in Swift.

Using optionals in your code

Once you've defined an optional value in Swift, it's important that we handle the possibility of a value being nil as well as the value being non-nil. Swift is pretty strict about this so optionals aren't used in the same way as you would use normal variables or constants.

For example, if we consider the theUser variable from earlier, we can't read the name from this property like this:

var theUser: User?

// Value of optional type 'User?' must be unwrapped to refer to member 'name' of wrapped base type 'User'
print(theUser.name)

The Swift compiler will tell us that we need to "unwrap" value of optional type User? in order to access its member name. This is the compiler's way of telling us that theUser may or may not be nil so we need to handle both scenarios.

Let's take a look at severals ways in which we can "unwrap" our optional.

Unwrapping with if let

If we're writing code where we want to only execute a part of our script or function in case the value isn't nil, we can use something called an if let unwrap. Here's what that looks like:

var theUser: User?

// somewhere else in the code...
if let userValue = theUser {
  print(userValue.name)
} else {
  print("the user is nil")
}

This if let attempts to read theUser and we assign it to a constant. This constant is then made available inside of the if's body where we know that userValue is of type User. Outside of our if body we won't be able to access userValue; it's only made available inside of the if. As needed, we can provide an else to handle scenarios where theUser is nil.

Note that the code above could be simplified a bit. Swift allows us to use something called a shadow variable (variable of the same name) for theUser which would change the if let as follows:

var theUser: User?

// somewhere else in the code...
if let theUser {
  print(theUser.name)
} else {
  print("the user is nil")
}

Note that theUser inside of the if body is not the same variable as theUser outside of the if body; it's a different property with the same name. For that reason, theUser inside of the if body is of type User and outside of the if body it's User?. This feature of Swift is nice when you're familiar with optionals but I find that sometimes it's better to provide a different name so that it's clear when you're using your unwrapped property or when you're using your optional property.

Unwrapping optionals with guard let

While if let is great for usage inside of code where it doesn't matter that much whether a value is or isn't nil, you sometimes want to make sure that a value isn't nil at the start of a function. With if let this would generally mean that you write an if let at the start of your function and then write the whole function body inside of your if let:

func performWork() {
  if let unwrappedUser = theUser {
    // do the work
  }
}

This works but it can lead to a lot of nested code. For scenarios where you only wish to proceed in your function if a value is not nil, you can use guard let instead:

func performWork() {
  guard let unwrappedUser = theUser else {
    return
  }

// do the work
// unwrappedUser is available to all code that comes after the guard
}

A guard allows us to ensure that our user has a value and that the unwrapped value is available to all code that comes after the guard. When we're using a guard we must provide an else clause that exits the current scope. Usually this means that we put a return there in order to bail out of the function early.

Unwrapping multiple properties

Both if let and guard let allow us to unwrap multiple properties at once. This is done using a comma separated list:

if let unwrappedUser = theUser, let file = getFile() {
  // we have access to `unwrappedUser` and `file`
}

The syntax for guard let is the same but requires the else:

guard let unwrappedUser = theUser, let file = getFile() else {
  return
}

  // we have access to `unwrappedUser` and `file`

Note that writing your code like this will require all unwraps to succeed. If either our user or file would be nil in the example above, the if body wouldn't be executed and our guard would enter its else condition.

Reading through optional chaining

When you're working with an optional and you'd like to get access to a property that's defined on your object, you could write an if let and then access the property you're interested in. You saw this earlier with User and its name property:

if let theUser {
  print(theUser.name)
}

If we know that we're only interested in the name property we can use a technique called optional chaining to immediately access the name property and assign that to the property we're writing the if let for instead.

Here's what that looks like

if let userName = theUser?.name {
  print(userName)
}

This is very convenient when we're in a situation where we really only care about a single property. If either theUser is nil or (if name is optional) name is nil the if body won't be executed.

We can use this technique to access larger chains of optionals, for example:

if let department = theUser?.department?.name {

}

Both theUser and department are optionals and we can write a chain of access using ? after each optional property. Once any of the properties in the chain is found to be nil the chain ends and the result is nil.

For example, if we just assign the chain from above to a property that property is a String?

// department is String?
let department = theUser?.department?.name

The name on the department property doesn't have to be a String? but because we're using optional chaining we'll get a nil value if either theUser or department is nil.

This leads me to one last method that I'd recommend for working with and that's using the nil coalescing operator.

Unwrapping optionals using nil coalescing

For any optional in Swift, we can provide a default value inline of where we access it. For example:

let username: String?

let displayName = username ?? ""

The ?? operator in the example above is called the nil coalescing operator and we can use it to provide a default value that's used in case the value we're trying to access is nil.

This is particularly useful when you need to provide values to render in a user interface for example.

You can also use this technique in combination with optional chaining:

// department is String
let department = theUser?.department?.name ?? "No department"

Now, let's take a look at one last method to unwrapping that I'm only including for completeness; this approach should only be used as a last resort in my opinion.

Force unwrapping optionals

If you're 100% absolutely sure that an optional value that you're about to access can never be nil, you can force unwrap the optional when accessing it:

print(theUser!.name)

By writing an ! after my optional variable I'm telling the compiler to treat that property as non-optional. This means that I can easily interact with the property without writing an if let, guard let, without optional chaining or without using nil coaslescing. The major downside here is that if my assumptions are wrong and the value is nil after all my program will crash.

For that reason it's almost always preferred to use one of the four safe approaches to unwrapping your optionals instead.

Subscribe to my newsletter