Swift for Ruby Devs: Optionals

Many times when designing software, we have to deal with the possible absence of a value. This is especially true when working with third party libraries, APIs, databases, or user input. A user may submit a form without filling it out, or we may not get any results back from a database query. Different programming languages deal with situations like these in different ways. In Ruby, for example, we can choose to handle these situations by returning nil. Like everything in Ruby, nil is an object. We can check whether a nil value is returned from a method, variable, or constant, then decide how our program behaves in the absence of a value. Swift, on the other hand, uses a concept known as Optionals.


What Are Optionals?


Swift's type-safe nature helps us in preventing errors, encouraging us to model our code more precisely by clearly specifying the types of values our code can work with. When dealing with the possibility of the absence of a value, we can specify that the value we expect to be returned is an optional type. We can declare any type as an optional. Integers, strings, classes, structs, enums, and even custom types, can all be declared as optionals. An optional represents two possibilities: The presence of a value or the absence of a value. Under the hood, an optional is just an enum with two cases: some and none. In the presence of a value, some holds the value itself as an associated value. In the absence of a value, none represents nil. To declare the optional version of any type, we simply add a ? to the type declaration. Here's a good example from the Swift docs that shows how optionals are used to deal with the possible absence of a value. Say we're trying to convert a string into an integer. A string can either contain values that represent a number (and can therefore be converted to an integer) or values that do not.

let possibleNumber = "123"  
let convertedNumber = Int(possibleNumber)  


In this scenario, convertedNumber is not of type Int but of type Int? (optional Int). This is because we can't be certain that possibleNumber contains a string representation of integers or if it also holds other characters such as letters. Since an optional value is returned, we must check if it contains a value. If a value exists, we can then unwrap the optional to retrieve the value it contains. If no value exists, nil will be returned.


Unwrapping Optionals


There are three different ways to unwrap optionals. The method we use can depend on the situation so it's important to understand them all so we can be better prepared to handle all cases.

Force Unwrapping

Force unwrapping unwraps an optional without first checking if it contains a value or not. Force unwrapping an optional should only occur if there is 100% certainty that an optional contains a value. If an optional that doesn't contain a value is force unwrapped, it will result in a runtime error. Optionals are force unwrapped by adding a ! at the end of the optional's name.

let possibleValue: Int? = Int("123")  
let definiteValue: Int = possibleValue!  


It's worth noting that force unwrapping is typically frowned upon since we won't always know with complete certainty that a value exists. There are safer methods to unwrap optionals that we'll be discussing next.

Optional Binding

Optional binding refers to the process of checking whether an optional contains a value and automatically unwrapping and binding it to a variable or constant if one exists. Optional binding can be accomplished with if statements, while loops, or guard statements.

let possibleValue: Int? = Int("123")

if let definiteValue = possibleValue {  
  print("There is definitely a value of \(definiteValue)")
} else {
  print("There is no value")
}


Swift allows you to include as many optional bindings and/or boolean conditions in an if statement, while loop, or guard statement as you see fit.

if let firstNum = Int("73"), let secondNum = Int("55") {  
  return firstNum + secondNum
}


If you're coming from the world of Ruby, as I am, then guard statements may not be familiar to you. You can kind of think of them as unless statements in Ruby, where the code within the block is executed if the condition being checked isn't met. guard statements are actually written as guard..else statements, and are better understood with an example. Say we have a method called responseBody that takes in a response object from an API request and returns an optional String since we can't be certain whether the API response contains a body or not.

We could do this with an if statement.

func responseBody(response: [String: String]) -> String? {  
  if let body = response["body"] {
    return body
  } else {
    return nil
  }
}


Or we could use a guard statement instead.

func responseBody(response: [String: String]) -> String? {  
  guard let body = response["body"] else {
    return nil
  }

  return body
}


guard statements are early-exit constructs because they immediately exit execution if a condition isn't met, and don't unnecessarily evaluate any other code. In this case, we must use a control transfer statement such as return, break, throw, continue, or a method that doesn't return anything. Readability can be improved with a guard statement by allowing us to perform the necessary checks first before executing any other code.

A final note is that any constants or variables created with optional binding are only available within the body of the if statement or while loop in which they are defined. Variables or constants created with a guard statement however, are available in the code that follows the statement.

The nil Coalescing Operator

The nil coalescing operator is a binary operator that is written as ??. The value evaluated on the left side of the operator (side A) must be an optional. The value evaluated on the right side of the operator (side B) must be of the same type that is stored inside of A. If the optional contains a value, it is automatically unwrapped and everything to the right of ?? isn't evaluated. If the optional is nil, the right side of the expression is evaluated.

let possibleNumber: Int? = Int("123")

let definiteNumber = possibleNumber ?? 3  
  => Int = 123


In this example, possibleNumber is an optional Int that contains an actual integer value; it is therefore unwrapped and set to definiteNumber. If possibleNumber were nil, then definiteNumber would default to 3.

let possibleNumber: Int?

let definiteNumber = possibleNumber ?? 3  
  => Int = 3


In this example, since we set possibleNumber as an optional Int without giving it an initial value, it was automatically set to nil. Therefore, definiteNumber defaults to 3.

In Ruby, this is similar to the || operator where side A is returned unless it is nil or false, in which case, side B is returned. There are, some differences with Swift's nil coalescing operator, however. We'll examine them next.

Because Ruby is a dynamically typed language we can have booleans on either side of a || expression, as well as different types.

false || 3  
  => 3

false || true  
  => true

"3" || 3
  => "3"


As stated before, Swift requires that the value on side A be an optional. The value of side B must be of the same type of the value that's potentially stored in A. If side A is of type Int? and side B is of type String or String?, then you'll get a compile time error.

let possibleNumber: Int? = Int("3")  
possibleNumber ?? "34"

=> binary operator '??' cannot be applied to operands of type 'Int?' and 'String'
let possibleWord: String?  
possibleWord ?? possibleNumber

=> binary operator '??' cannot be applied to operands of type 'String?' and 'Int?'
33 ?? "34"  
=> binary operator '??' cannot be applied to operands of type 'Int' and 'String'


If both sides of the operator are the same type but side A is a non-optional, you won't get a compiler error but you will get a warning stating that the right side will never be evaluated. In this case, the nil coalescing operator isn't doing anything useful.

false ?? true  
  => false
  left side of nil coalescing operator '??' has non-optional type 'Bool', so the right side is never used
3 ?? 34  
  => 3
  left side of nil coalescing operator '??' has non-optional type 'Int', so the right side is never used


Optional Chaining


Optional chaining is the process of calling methods, properties, or subscripts on an optional that might be nil. If the optional isn't nil, then the return value of the method/property/subscript call will be returned as an optional. So if a method call is supposed to return a value of type Int, it'll instead return a value of type Int?. If the optional in question does not contain a value, then nil will be returned. Many method/property/subscript calls can be chained together and if any link in the chain fails, the entire chain fails gracefully and returns nil. To employ optional chaining, simply add a ? after the optional, then call the method/property/subscript.

let possibleWord: String? = nil  
possibleWord?.characters.count  
  => nil
let possibleWord: String? = "abc"  
let wordCount = possibleWord?.characters.count  
  => 3

type(of: wordCount)  
  => Optional<Int>.Type


When an optional does contain a value, we can unwrap it via optional binding.

let possibleWord: String? = "abc"

if let wordCount = possibleWord?.characters.count {  
  print("wordCount is of type \(type(of: wordCount)).")
}
  => wordCount is of type Int. 


If you're a Ruby developer learning Swift, you can think of optional chaining as using the #try method in a Rails application to allow you to chain method calls together on objects that might be of NilClass and gracefully fail if a method is called on nil.

"abc".try(:length)
  => 3

nil.try(:length)  
  => nil


Optional chaining can be used to check whether a method call is successful even if that method doesn't return anything. Methods that don't return anything in Swift, have an implicit return type of Void. Therefore, a successful method call via optional chaining on a method with no return value, will return Void?. We can check this against nil to see if a method call was successful or if the optional doesn't contain a value.

if someOptional?.printToConsole() != nil {  
  return "Method call successful"
}


One last note about optional chaining is that it can be used to set a value on a property of an optional that might be nil and check whether the property was set successfully. If a property on an optional was set successfully via optional chaining, this will result in a return type of Void? that can be checked against nil.

if (someOptional?.someProperty = "new value") != nil {  
  return "someProperty was set successfully"
}


Summary


Understanding optionals and how to use them is important when writing applications that can withstand uncertainty from third party services, user input, and even our own code. You will come across optionals a lot when using various iOS SDKs and knowing how to interact with them will help you in preventing errors by explicitly accounting for these uncertainties in your code.