Tidbit: Optionals in Swift

Introduction

You may recall seeing something like this in lecture:

var greeting: String? = "Hello" 

Anyone with programming experience can probably figure out that this is some sort of variable assignment. The String "Hello" is assigned to greeting. But what does the question mark mean? That looks unfamiliar. Is the String confused? Is this just weird Swift syntax for the string type?

We will talk about optionals in Swift to clear up all this confusion.

So, What Are Optionals?

As we mentioned in lecture, Swift is a type safe language. For example, that means you cannot pass an Int to a function that expects a String. If you try to do this, you will see a compile-time error.

In more effort for type safety, Swift does not allow direct reference to nil. If you try to set a reference to nil, you will get a compile-time error. For example,

var str: String = nil // Swift won't let you do this

But often times in code we want to use nil. So how does that work? What if you want to return nil if an object is not in an array or something like that? The answer is optionals.

In Swift, an optional type is a variable or constant that is or is not nil. If the optional type is not nil, then it has some value. The name "optional" kind of says it all.

Any type in Swift can become an optional. All you have to do is add a question mark to the end of the type to make the type an optional. For example:

var state: String? = "Pennsylvania"
var speedLimit: Int? = 60
var jerry: Person? = Person("Jerry") // Types that you declare can be optional too

Whenever you create an optional reference, you are pretty much making a wrapper to hold a value of the type you specify or to hold nil. Then in order to get the value out of the optional, you have to unwrap the optional. This is done by adding an exclamation mark to the name of the variable referencing the optional.

var num1: Int? = 7
var num2: Int? = 5
var sum: Int = num1! + num2! // Unwrap Int? to get Int

So what is the point of this? Why not just allow references to nil? The answer is safety. Non-optional types cannot be nil, this provides some guarantee that you will not accidentally dereference nil. And since you must unwrap an optional to get the value it contains, this provides a reminder to check for nil otherwise.

class Class { 
  var name: String
  var students: [Student]

  init(name: String, capacity: Int) {
    self.name = name
    self.students = [Student]()
  }

  func enrollInClass(student: Student) { // Person is not optional, so cannot be nil
    students.append(student) 
    student.numClasses++ // So, we don't need to check if student is nil
  }

  func findStudentByName(name:String) -> Student? {
    for student in Students {
      if(student.name == name) {
        return student
      }
    }
    return nil // return type is an optional type, so this is okay
  }
}

Notice how the findStudentByName function returns an optional, so it is okay that it returns nil if the student is not in the class. So, if we test this code:

var jerry: Student = Student(name: "Jerry", numClasses: 4)
var bob: Student = Student(name: "Bob", numClasses: 5)

var iosDevelopment: Class = Class(name: "iOS Development", capacity: 80)

iosDevelopment.enrollInClass(jerry)
iosDevelopment.enrollInClass(bob)

var sam: Student? = iosDevelopment.findStudentByName("Sam")

if (sam != nil) { // check if optional contains nil 
    println("\(sam!.name) is in \(iosDevelopment.name))") // okay to unwrap because not nil
}
else {
    println("Sam is not in iOS Development")
}

In this case, we should see "Sam is not in iOS Development" print in the console since sam is not in the class.

Optional Chaining

So since optionals help remind you to check for nil, you can imagine that you probably need to do a lot of if checks. And that's kind of annoying. Say you have something like this:

func findNotNil(num1: Int?, num2: Int?, num3: Int?) -> Int? {
    var returnVal: Int? = nil
    if(num1 != nil) {
        returnVal = num1
    }
    else if(num2 != nil) {
        returnVal = num2
    }
    else if(num3 != nil) {
        returnVal = num2
    }
    return returnVal
}
    

This function is pretty simple, but it looks really ugly. Luckily Swift has a feature called optional chaining. Basically, you can turn the above function into this:

func findNotNil(num1: Int?, num2: Int?, num3: Int?) -> Int? {
    return num1 ?? num2 ?? num3
}
    

Wow, this looks much nicer. Basically with optional chaining you can use the ?? operator to check if the value on its righthand side. If it is nil, then use the value on the lefthand side. You can chain together multiple checks to compact a lot of these if checks.

You can also use optional chaining when typecasting. Instead of doing this:

var animal : Animal?
var cat: Cat?
...

if(animal != nil) {
    cat = animal! as Cat
}
else {
    cat = nil
}
    

You can make a much more compact check like this:

var animal : Animal?
var cat: Cat?
...

cat = animal as? Cat
    

Are Optionals Always Safe?

So now hopefully you see why optionals can help make Swift safe. Note: the keyword is can. Unfortunately, optionals are not quite fail-safe. There is a way to implicitly unwrap optionals so that you don't have to unwrap all the time. For example:

var number: Int! = 7

By placing the exclamation mark after the type we are saying this is an optional type that will automatically unwrap when you try to get its value. So, we are allowed to just do this:

var sum: Int = 2 * number

If we tried to do this with just a regular optional, we would see a compile-time error complaining about how number was not unwrapped. However, since we specified that number should be implicitly unwrapped, then we will not see this error.

This can be problematic. The whole reason why optionals were nice was because the compile-time error warning about unwrapping was a reminder to check that the optional value was not nil. By implicitly unwrapping an optional, you may unwrap an optional that is holding nil. This can give a run-time error, and that is not good. For example, examine this:

var price: Float = 5.00
var salesTax: Float! = nil
var total: Float = price * salesTax

Since salesTax is an optional, it can be nil. But it is implicitly unwrapped, as indicated by the exclamation point. Therefore, during runtime if salesTax is used at all, we will be using nil. So, in the above example we are multiplying by nil which will give a runtime error.

Generally it safer to use regular optionals instead of implicitly unwrapped optionals. While Swift aims to be safe, a lot of safety is still up to the programmer. If you know that some variable will always refer to a value and will not change, then it may be okay to implicitly unwrap. But it's better to be safe than sorry.