DEV Community

Monica Granbois
Monica Granbois

Posted on • Originally published at monicagranbois.com

Swift Optionals

In a previous post I kicked off my dive into Swift by covering the basics: syntax, ranges, and tuples. I decided to tackle optionals next. This lead to quite the investigation. But, I now have a better understanding of what optionals are and where to use them. This post is about what I learned.

What is an optional?

An optional represents the scenario where a value may exist or not. An optional is represented by the ? symbol.

Example:

var a: Int?
Enter fullscreen mode Exit fullscreen mode

In the above example, the variable a has a type of Optional Integer. Any type can be made an optional type by adding a ?.

Here, a does not have a value; it is nil. Swift uses nil to represent the absence of a value. The ? is an indicator for a developer to be careful. Any code using a will need to handle a possible nil value.

Why use optionals?

Why are optionals needed? To help explain I will use a mailbox and mail as an example. A mailbox may or may not have mail in it. In this case, the mail is the optional; it exists or it does not.

Below, I have used Java to create MailBox and Mail classes that do not use optionals. I made the following restrictions for simplicity:

  • Mail uses Strings for the to and from addresses. I did not include fields for street, city etc.
  • A MailBox can only contain one Mail. (Yes, in real life a mailbox can contain many pieces of mail. But for simplicity in this example, I've restricted it to one piece of mail.)
public class Mail {

    private String from;
    private String to;

    public Mail(String from, String to) {
        this.from = from;
        this.to = to;
    }

    public String getFrom() {
        return from;
    }

    public String getTo() {
        return to;
    }
}
Enter fullscreen mode Exit fullscreen mode
public class MailBox {

    private Mail mail;

    /**
     *
     * @return - null if the MailBox does not have Mail
     */
    public Mail getMail() {
        return mail;
    }

    public void setMail(Mail mail) {
        this.mail = mail;
    }
}


Enter fullscreen mode Exit fullscreen mode

Anyone using this code will need to be aware of the methods which may return null. One possible solution is to document the code. However, there are two potential problems with relying on documentation:

  1. The code is not documented.
  2. The documentation is not correct:
    1. It may have been correct at one time. But, the code changed and the documentation was not updated.
    2. The developer may not have considered all possible return cases.

Given the code above, someone may write a method like the following:

void checkMail(MailBox mailBox) {
    if(mailBox != null) {
        Mail mail = mailBox.getMail();
        if (mail != null) {
            System.out.println("Mail from: " + mail.getFrom().trim());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

At first glance, the checkMail code may look fine. However, there is a bug in it. The mail.getFrom() method can return a null. So, the use of trim() causes a null pointer exception for that scenario. Oh, no!

Optionals prevent the above scenario because their usage state whether a value may be nil or not. A type that is not an optional cannot have a nil value. This means a developer does not need to rely on documentation or reading the code to figure out if a value can be nil. The type states whether a nil value is possible.

Both Swift and Java (as of version 1.8) support optionals. Below, the mailbox analogy is rewritten in Swift using optionals.

class Mail {
    let to: String
    let from: String

    init(to: String, from: String) {
        self.to = to
        self.from = from
    }
}
Enter fullscreen mode Exit fullscreen mode

In this version of the Mail class, the to and from fields are not optional. This means setting the value to nil will cause a compiler error.

class MailBox {
    var mail: Mail?

}
Enter fullscreen mode Exit fullscreen mode

In this version of the MailBox class, the mail property is a type of optional Mail. It may be nil. We do not need to rely on the documentation to convey this fact.

import Foundation
func checkMail(mailBox: MailBox) {
    if mailBox.mail != nil {
        print("Mail from: \(mailBox.mail!.from.trimmingCharacters(in: .whitespacesAndNewlines))")
    }
}
Enter fullscreen mode Exit fullscreen mode

In this version, a nil check is not performed on the mailBox parameter because it is not an optional. It must exist. The mail property is checked for a nil value because it is an optional.

Swift and optionals

Now that we know what optionals are and why we would use them, let's look at how Swift uses optionals.

Forced Unwrapping

Think of an optional as a wrapper around a type. We have to unwrap the type from the optional to use it. For example, unwrapping Mail from Optional Mail. Forced unwrapping means retrieving the value without checking for nil . Adding the ! symbol to the end of a value forcibly unwraps it.

In the checkMail function above, mailBox.mail! forcibly unwraps the Optional Mail type to the Mail type. The from field can then be used. Since the from field is not an optional, it cannot be nil. Therefore, the trimmingCharacters function is safe to use.

Forced unwrapping can be dangerous because it does not check for nil values. The checkMail function above performs a nil check (mailBox.mail != nil) before using the mail property. So, it is safe to force unwrap it. However, if the code did not and mail was nil, the mailBox.mail!.from code would cause a crash.

Implicitly unwrapped optionals

In some cases, it may be certain an optional will never be nil after initialization. Having to check and unwrap this value each time can be cumbersome. Implicitly unwrapping such an optional makes it easier to work with. Placing a ! after the type creates an implicitly unwrapped optional. It has the form:


let name: Type! 
Enter fullscreen mode Exit fullscreen mode

In the example below, myMailBox has a Mail item. Assigning myMailImplicitlyUnwrapped to myMailBox.mail makes myMailImplicitlyUnwrapped an optional. But, we know it will never be nil. Having to unwrap it every time would be annoying. Declaring myMailImplicitlyUnwrapped as an implicitly unwrapped optional makes it like a non-optional value. Although, it is still an optional behind the scenes. A ! or ? is not needed to access the value throughout the code.

let myMailBox = MailBox()
myMailBox.mail = Mail(to: "Alice", from: "Bob")

let myMailImplicitlyUnwrapped: Mail! = myMailBox.mail

print(myMailImplicitlyUnwrapped.to) // the ! is not needed
print(myMailImplicitlyUnwrapped.from)

Enter fullscreen mode Exit fullscreen mode

In contrast, the example below uses the ! each time the myMailOptional is used.

let myMailBox = MailBox()
myMailBox.mail = Mail(to: "Alice", from: "Bob")

let myMailOptional: Mail? = myMailBox.mail

print(myMailOptional!.from) // The ! is needed each time the value is used.
print(myMailOptional!.to)
Enter fullscreen mode Exit fullscreen mode

Dangers of using !

Using ! is risky. If the optional is nil, using its value can lead to the program crashing!
However, there are some use cases for using !:

  • Convenience
  • Compatibility when working with Objective-C platforms
  • Fail Fast. For example, if a resource is missing then fail immediately rather than continuing.

The best practice is to avoid them whenever possible.

Optional binding

Optional binding is an alternative to using a nil check. It checks if an optional has a non-nil value and assigns that value to a temporary constant or variable. That temporary constant/variable's scope is within the if/while block. Optional binding has the form:

if let temp = optional {
    //temp is only available within this code block
    //code to execute
}
//temp is not available here.
Enter fullscreen mode Exit fullscreen mode

Below, the checkMail function is rewritten to use optional binding:

import Foundation
func checkMail(mailBox: MailBox) {
    if let mail = mailBox.mail {
        print("Mail from: \(mail.from.trimmingCharacters(in: .whitespacesAndNewlines))")
    }
}
Enter fullscreen mode Exit fullscreen mode

If the mailBox.mail value is not nil then its value is assigned to the temporary mail constant. The constant's scope is only available within the if block. The ! is not needed within the if block because mail's type is Mail, not optional Mail. If the mailBox.mail value was nil then the if block is not executed.

Optional Chaining

Optional chaining allows access to optionals within other structures. It provides a safe way of accessing optionals which may contain a nil valued. To use optional binding, place a ? symbol after the value. This is like using the ! symbol when force unwrapping a value. But, optional chaining will not crash a program on a nil value.

Looking at the checkMail function again, it can be rewritten using optional chaining:

import Foundation
func checkMail(mailBox: MailBox) {
    if let from = mailBox.mail?.from {
        print("Mail from: \(from.trimmingCharacters(in: .whitespacesAndNewlines))")
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, I have modified the optional binding part to retrieve the from property from the Mail class. Optional chaining provides a fail-safe way of accessing the optional Mail type. If mail exists then Swift will get the value of the from property. Since mail may be nil, then the result of accessing mailBox.mail?.from will return a String? rather than a String. Optional binding is then used to get the from value.

Optional chaining will make a non-optional value an optional. But, this process does not wrap optionals again. For example, a String? will not become a String?? through optional chaining.

I've modified the Mail class to include a stamp property with a type of Float?. (Junk mail flyers do not have stamps). In this example, I've rounded the stamp value (for some contrived reason for this example):

class Mail {
    let to: String
    let from: String
    var stamp: Float?

    init(to: String, from: String) {
        self.to = to
        self.from = from
    }


}


let mailBox = MailBox()
mailBox.mail = Mail(to: "Alice", from: "Bob")

let rounded = mailBox.mail?.stamp?.rounded()

Enter fullscreen mode Exit fullscreen mode

Some notes about this code:

  • access to the stamp property is by mailBox.mail?.stamp?.rounded() and NOT mailBox.mail?.stamp??.rounded()
  • the rounded() function returns a Float. But, the return value will be Float? because of the optional chaining. So, the rounded constant is an optional!

All of my examples used properties. But, optional chaining applies to methods and subscripts too.

Nil coalescing

Nil coalescing unwraps an optional and return its value or a default value in the case of nil. The nil coalescing symbol is ?? and has the form:

optional ?? defaultValue
Enter fullscreen mode Exit fullscreen mode

Below is an example using nil coalescing with the stamp property.

let stampAmount = mailBox.mail?.stamp ?? 0.0
Enter fullscreen mode Exit fullscreen mode

Here, stampAmount will be either the value of stamp or the default value, 0.0. Its type is Float.

map and flatmap

The Optional Type implements the map and flatmap functions. One use of these functions is as an alternative to nil checks.

For example, suppose we have an openMail function. This function takes a Mail parameter and returns the contents of it as a String. Note that the Mail parameter is a non-optional type. For this example, I've hardcoded the function to return a String. Please imagine that a real implementation would actually do something.

func openMail(_ mail: Mail) -> String {
    //do processing
    return "mail contents"
}
Enter fullscreen mode Exit fullscreen mode

To use this function we might write code like the following:

if let mail = mailBox.mail {
    let content = openMail(mail)
    //do stuff with content
    print(content)
}
Enter fullscreen mode Exit fullscreen mode

Here, optional binding gets the non-nil value of the mail property. That value is then passed to the openMail function. This code can be rewritten using the map function.

The map function allows us to use an optional with a function that has a non-optional parameter. Map unwraps the optional and, if the value is not nil, passes that value to the function. Example:

if let mailContent = mailBox.mail.map(openMail) {
    //do stuff with mailContent
    print(mailContent)
}
Enter fullscreen mode Exit fullscreen mode

This code is a little bit more concise. But what is wrong with the nil check? Well, suppose we changed the openMail function to return a String?:

func openMail(_ mail: Mail) -> String? {
    //do processing
    let contents: String? = "mail contents"
    return contents
}
Enter fullscreen mode Exit fullscreen mode

We could write code like the following.

if let mail = mailBox.mail {
    if let openMail = openMail(mail) {
        print(openMail)
    }
}

Enter fullscreen mode Exit fullscreen mode

Now the code has nested if blocks. This structure can be hard to maintain and can be bug-prone. Rewriting with flatMap gets rid of the nested if statements.

if let mailContent = mailBox.mail.flatMap(openMail) {
    print(mailContent)
}

Enter fullscreen mode Exit fullscreen mode

More on the optional type

Behind the scenes, the optional type is an enumeration with two values, none and some<Wrapped>.

Swift is open sourced, so we can look at the code on GitHub. The following is from the Optional.swift file:

public enum Optional<Wrapped> : ExpressibleByNilLiteral {
  // The compiler has special knowledge of Optional<Wrapped>, including the fact
  // that it is an `enum` with cases named `none` and `some`.

  /// The absence of a value.
  ///
  /// In code, the absence of a value is typically written using the `nil`
  /// literal rather than the explicit `.none` enumeration case.
  case none

  /// The presence of a value, stored as `Wrapped`.
  case some(Wrapped)

Enter fullscreen mode Exit fullscreen mode

It is possible to use the enumeration form of an optional. But, it is a verbose style of coding as demonstrated below:

let number: Optional<Int> = .some(5)
let empty: Optional<Int> = .none

switch number {
case .some(let n):
    //n is of type Int, it is the unwrapped value of the number optional
    var z = n * 7
case .none:
    print("none")
}
Enter fullscreen mode Exit fullscreen mode

The ? operator is syntatical sugar for accessing optionals. The following are equivalent:

let number: Optional<Int> = .some(5)
let empty: Optional<Int> = .none

let sugarNumber: Int? = 5
let sugarEmpty: Int?
Enter fullscreen mode Exit fullscreen mode

Summary

In summary:

  • Optionals provide safety for programmers by eliminating null pointer type errors.
  • Swift provides both safe and unsafe ways to access an optional's value.
    • Optional binding provides a safe and convenient method to work with an optional.
    • Optional chaining safely unwraps an optional using the ? operator.
    • Forced unwrapping uses the ! operator. It does not provide any safety as a nil value can cause the program to crash.
    • An implicitly unwrapped optional is an optional but used like a non-optional type. It is a convenience style used when the value will never be nil after initialization.
  • Nil coalescing unwraps an optional and return its value or a default value in the case of nil.
  • map and flatmap functions provide a way to transform the optional's value.
  • Behind the scenes an optional is an enum with two cases none and some<Wrapped>.

Thank you for reading! Please let me know if anything is wrong in this post, as I am still learning Swift.

References

This post was originally posted on my blog

Top comments (2)

Collapse
 
fassko profile image
Kristaps Grinbergs

Thanks for the great article. There is also a great talk from Rob Napier about optionals. ;) youtube.com/watch?v=Q1Tayh4unMw

Collapse
 
monicag profile image
Monica Granbois

I still have some questions about where implicitly unwrapped optionals are used. My next blog post (which I am already working on) will cover those questions. :)