RSSBlog

Swift: Coding with Options?!??

Swift utilizes a concept borrowed from type theory called "Option type". What it means is that a variable of a certain type can be also declared to be "optional". In other words, it may have a value set to it, or maybe not. The program will have to check this every time it tries to read the value of this variable.

"But why do you need this??" you might ask. Well, that's a long story. So let's get straight to it:

A little bit of history

The year was 1965. A computer scientist, Tony Hoare, was working with colleague Niklaus Wirth on designing a new programming language: Algol W. It was based on (and intended to replace) Algol 60, a successful programming language introduced in 1958 and later updated in 1960. Algol W was designed to include many advanced features such as debugging, profiling, and typed pointers (all three new concepts, and extremely innovative at the time). However, one thing was missing…

You see, the straightforward way of initializing a variable of integer type, for example, is by setting it to zero. A straightforward way of initializing a variable of string type could be setting it to an empty string. But how do you initialize a pointer variable? If it is pointing to a raw type, such as an integer, then you might set it to a new integer storage (an actual integer variable) that is set to zero, and then point the pointer to that. However, if the variable is a pointer to an object1 type, then things start getting complicated.

1: "Objects" here are just composite types, such as structs in the C language, that might contain multiple internal variables (storages) of various types. The notion of Objects with classes and methods only came a few years later, in the 1970s and 1980s, with SmallTalk.

To initialize an object pointer type you could, theoretically, follow the example of the integer pointer type by creating a new storage and setting all internal storages of the object to their respective initializer values. But what happens if one of the composite storages is also a pointer to an object (say, a "queue-item" object that contains an object pointer reference to the next "queue-item" object in the queue)?

You'd suddenly be trapped inside a (possibly) never-ending, recursive thread of objects creation and initialization, which could potentially be useless if they were to be overwritten as soon as they were initialized. And we're not even considering the problem of cyclic references.

Keep in mind that the computers at the time would run at the scale of a few thousand computations per second (kHz).

There had to be a simpler way…

Then comes the NULL reference.

Hoare believed he had found a simple solution: He proposed that any pointer could be set to a simple "empty" value, meaning that it is not currently "pointing" to anything. You wouldn't have to descend into a long series of recursive object initializations even before the objects were required, and therefore the program would be able to run much faster.

However, what was believed to be a "simple" solution came with a hefty price. But before we get to that, have some more context:

In the world of typed and compiled languages, the compiler can do many checks in your code for you. It can ensure you never violate variable types, for example (i.e. by assigning a float value to an integer variable).

Some languages even include part of those checks during runtime. Hoare's own Algol W included an array subscript2 check during runtime that prevented a subscript access from violating the array's bounds, and even though it made programs run slower, programmers at the time requested the checks to be kept when Hoare's team considered removing them.

2: In a language with array subscript access check on compile time, the compiler will complain if you try to read the value arrayVar[10] when arrayVar was only declared to have 5 elements, for example.

But, as Hoare noted, if you structure you data using objects instead of pure arrays, then there would be no way for you to cause a subscript violation during runtime, since you can not possibly point to an internal variable of an object if that internal variable doesn't exist. The compiler would identify this violation during compilation.

For example, if you defined an object type called "Bus" that was composed of the internal storages (variables) "Power" and "MaxPassengers", there would be no way for you to reference an internal storage called "TicketPrice", since the compiler would know that the "Bus" object has no storage with such name.

Because of this, Hoare's team decided they did not need to add runtime checks for internal object storage pointer violations. That would make the programs run much faster compared to the subscript checks required for arrays.

However, the same problem that they "fixed" by using null references came back to bite them: What if one of the internal storages of an object is a pointer type. Then what if you never set any value to that pointer (after the initialization to null)? Then what if you assume it was set, and try to access its value by simply descending through it?

Then comes the NULL r deference.

This was no cheap fix.

Trying to access a null pointer (aka "deferencing" it) means the program has lost control of its own data storage, and it is the programmer's fault! The program reached a corner case where the programmer assumed the pointer would have been set to some value, but it wasn't!

If the program was reading from memory, the error might be recoverable. However, if it was writing to memory, then many things can happen. The program might crash due to a memory access violation3, the program might end up writing to an unrelated memory space, corrupting user data, etc…

3: A memory access violation is when a program tries reading/writing from/to memory space it does not own (like another program's memory). The Operating System hates that with all its kernel.

The "fix for the fix" is that whenever the program needs to deference a pointer that might be null (or better, always), the code needs to check if it is set to null. If it is, that means the program execution path will need to change to avoid problems.

And even though Algol W was not chosen as the official descendant of Algol 60, the concepts introduced by it influenced most of the languages developed afterwards, including the NULL pointer reference in C, C++, Java and many other languages that came later in time.

Tony Hoare himself calls the null reference his "Billion Dollar Mistake", because of the countless programmer hours spent fixing, user hours spend complaining, and real data damage/loss caused by this "simple fix".

It is indeed a very bad thing, but I don't think anyone should blame Hoare (or anyone) for this "mistake". If you think about it, the concept of a "null" pointer would have been invented by someone else, eventually. It is way too simple and effective of a fix to be ignored. If anything, the null reference was just "waiting to be discovered".

Many attempts were made to try and fix the null deference problem. Java, for example, uses "exceptions", which are "thrown" back to the caller whenever the program fails. This solves many problems with situations where a routine would return null to indicate it failed, a common cause for null deferences.

However, not even that is very good, since declaring try/catch blocks in Java can be very cumbersome, at times.

And that's why Swift brought the "Option type" from type theory. It (mostly) solves the null reference problem in a very elegant way.

Then comes the Optional way

The beauty of this solution is that if you use a type to indicate a variable is "not set", then you can invoke the compiler's help to check if you are doing everything correct.

"But what about the actual type of my variables? I want my ints back!" you could say. Well, guess what? Because of another Swift feature, you don't have to compromise on strong-typing at all.

That other Swift feature is called "Generic types". Those are types that reference other types. It might sound complicated, but it is very simple: A generic is like a placeholder for any other type, which can be set depending on the actual program requirements.

The "Optional type" in Swift is defined as an enumeration with two possible values: It can either be "nothing", or "something" of generic type X.

enum Optional<X>
{
    case none
    case some(X)
}

And then, when you say a variable is of type "Optional", you also need to define the type "X" it will have when its case is set to "some":

let variableA: Optional<Int> = .some(1)
let variableB: Optional<Double> = .none

However, Swift makes things simple for you because it also defines the "Optional" type declaration as an unary operator:

let variableA: Int? = 1
let variableB: Double? = nil

Both code blocks above have the exact same behavior. In both a variable with name "variableA" and of type "Optional type Int" is declared and initialized with the value "1", and a variable with name "variableB" and of type "Optional type Double" is declared and initialized with the value "nothing". The second block is just a shorthand version of the first.

Now, whenever you wish to use either variableA or variableB, you will have to check if it is set to a value ("some") or not ("none"). This can be done very easily with the ?, !, and ?? operators.

Notice nil is not called null in Swift exactly to avoid any possible confusion with the old NULL from the past. They are very different even though they serve similar purposes.

Unwrapping the wrappers

In Swift, when you use an Optional type, checking if its internal value is set, and then reading it, is a process called "unwrapping". You can pretend the optional type variable is covered with an opaque wrap, and that you have to remove this wrap to see the value inside. If removing the wrap reveals there is nothing there, then you need to change the course of your program.

The advantage here, again, is that the compiler will help you with these checks. Since an "Optional type Int" is different from a simple "Int" type, the compiler will be very vocal about your attempts to convert the first into the latter without unwrapping it first. From the compiler's perspective, it will be equivalent to setting a double value to an integer variable, for example.

To unwrap a variable, you can use the following operators:

  • ? – Try unwrapping – If a value is set, returns the value; otherwise, gives up the entire remaining of the expression, safely proceeding to the next line.
  • ! – Force unwrapping – If a value is set, returns the value; otherwise, causes a fault and the program terminates.
  • ?? – Nil coalescing – If a value is set, returns the value; otherwise, returns the value to the right-hand side of the operator. This is an infix operator.

Examples for all those follow:

We will reuse the variableA and variableB variables from the previous section.

Safe check with explicit fallback
if let safeInt = variableA
{
    print("I can safely use 'safeInt'! It is \(safeInt).")
}
else
{
    print("'variableA' is not set to an integer!")
}

if let safeDouble = variableB
{
    print("I can safely use 'safeDouble'! It is \(safeDouble).")
}
else
{
    print("'variableB' is not set to a double!")
}

Executing the previous block would result in the output:
I can safely use 'safeInt'! It is 1.
'variableB' is not set to a double!

Safe check with nil coalescing
let safeInt = variableA ?? 5
let safeDouble = variableB ?? 0.5

print("I can safely use 'safeInt'! It is \(safeInt).")
print("I can safely use 'safeDouble'! It is \(safeDouble).")

Executing the previous block would result in the output:
I can safely use 'safeInt'! It is 1.
I can safely use 'safeDouble'! It is 0.5.

Unsafe check
print("Let's see if I can use 'variableA': It is \(variableA!).")

Executing the previous block would result in the output:
Let's see if I can use 'variableA': It is 1

print("Let's see if I can use 'variableB': It is \(variableB!).")

Executing the previous block would result in a fault, and the program would crash, since variableB was set to nil (or .none) and we tried to "force-unwrapping" it.

Try unwrapping – skip if fail
if let safeInt = variableA?.advancedBy(15)
{
    print("I can safely use 'safeInt'! It is \(safeInt).")
}
else
{
    print("'variableA' is not set to an integer!")
}

if let safeDouble = variableB?.advancedBy(15.0)
{
    print("I can safely use 'safeDouble'! It is \(safeDouble).")
}
else
{
    print("'variableB' is not set to a double!")
}

Executing the previous block would result in the output:
I can safely use 'safeInt'! It is 16.
'variableB' is not set to a double!

Warning: The ! operator does not like you

The ! operator has a very shallow use scope. If you use it everywhere, then you will be simply reintroducing the old null deference problems in your program (and you will make Tony Hoare sad). The operator ! is to be used in places where the variable is unequivocally set to a value, and you are 175% sure of that (even in those cases I would advocate for you to use ?, with very few exceptions). One example might be reading a variable of Optional type right after you declare it and initialize it with an actual value, and because you plan to set it to nil depending on your code flow.

// Emojis are cool
var likesEmojis: Bool? = true

// I *know* I like them!
print("Do I like emojis? \(likesEmojis!)")

let knownEmojis = "🤡"
likesEmojis = nil

if let safeILikeEmojis = likesEmojis
{
    print("Do I like emojis? \(safeILikeEmojis)")
}
else
{
    print("Do I like emojis? I don't know anymore...")
}

Executing the previous block would result in the output:
Do I like emojis? true
Do I like emojis? I don't know anymore...

Mind your own Optionals

Now that you know about the usage, and historical reason, of Swift's Optional type, I hope you will be able to make more elegant and safe programs.

Happy Swifting!

Moar??

You can find a lovely talk by Tony Hoare himself about his "Billion Dollar Mistake" right here (the video in the linked page requires adobe flash, which is arguably another "Billion Dollar Mistake" itself).