Swift’s Optional Chaining as an Experiment in Modifying Programmer Behavior

Much (virtual) ink has been spilled on the topics of learning Swift and transitioning from Objective-C to Apple’s new language, so I’ll keep this one light. While working on SwiftSquareCam (in progress), a number of Swift’s features (or idiosyncrasies) stood out, not merely for being different or new, but for forcing me to program using certain patterns and idioms. It then occurred to me to think of these features in the context of Apple’s desire to make the average programmer more productive. So what are they?

Optional Chaining

Like Objective-C, Swift has a nil type. Unlike Objective-C, however, Swift does not permit ordinary variables that can hold a reference to a class instance to be assigned a nil value. At first blush, this may seem problematic for consumers of iOS APIs that may return nil or assign nil to an output variable, e.g., in the event of failure. If you try, say:

var foo: AnyObject = nil

you will encounter the error Type 'AnyObject' does not conform to prototype 'NilLiteralConvertible'.

Therefore, Swift provides the optional type modifier, expressed as a ? at the end of a type specification:

var bar: AnyObject? = nil

An optional type can hold a nil value; thus, it can be used with API calls that return nil or otherwise assign output variables to nil. However, the value and members of an optional variable cannot simply be accessed – after all, it could be nil.  The Swift compiler is aware of this state of affairs and enforces certain semantics for accessing optional values. Attempting the following:

var foo: String? = nil
let empty:Bool = foo.isEmpty

generates the (possibly unhelpful) error message 'String?' does not have a member named 'isEmpty'. Instead, you can access the value as follows:

var foo: String? = nil
if let empty = foo?.isEmpty {
    println("Foo exists and isEmpty=\(empty)")
}
else {
    println("Foo is nil")
}

A number of things are going on here. First, Swift does not permit ordinary assignments to be used as conditionals. Notably, as here, even statements that assign a value to a Bool variable cannot be used as such. Only when accessing an optional value in the manner above can one use the assignment as a conditional, and in that instance it has the meaning implied above: if the optional value is non-nil, then then the assignment/access will evaluate to true and execution will continue in the first conditional block after the if statement.

Of course, one can also test the validity of an optional the old-fashioned way, using

if foo == nil { ...

and often one will want to do that.

Forced Unwrapping

By using “forced unwrapping,” you can still direct the compiler to treat an optional as a valid instance of the underlying type:

var foo: String? = nil
let forced_empty:Bool = foo!.isEmpty

Unlike the earlier example that employed neither chaining (with ?) nor unwrapping (with !), this code will compile. Nevertheless, it will generate a runtime error when trying to access a member of the nil reference. Although one might guess that one can simply wrap the call in a try block and catch the runtime error as an exception, Swift at this time does not  provide exception handling. Therefore, one should test for nil the old-fashioned way noted above.

The Two Approaches Compared

The astute reader will have noted that optional chaining merely embodies good programmer habits. After all, one always should validate return and output values from API calls. However, often developers take shortcuts, as when writing a quick-and-dirty test to evaluate an API feature. Unfortunately, these shortcuts can outlive their intended purpose, sometimes even making their way into production code.

I concede that forced unwrapping demonstrates that Swift does not set out to prevent every potential error associated with nil references. Yet forced unwrapping is clearly the inferior alternative, particularly in light of the lack of exceptions in Swift’s language model. Although some might find it annoying to declare an “extra” variable to facilitate optional chaining, the resulting code seems more intuitive and avoids the runtime errors associated with nil references. Apple is trying to move programmers away from directly accessing members of references that might be nil and towards optional chaining; even the syntax of forced unwrapping – the exclamation point – serves as a quiet reminder that one is doing something that could go very, very wrong.

From brief experience, optional chaining, once one gets used to its semantics, is a neat way to handle nil values and provide a framework for a routine to fail gracefully. It even provides the means to attempt to access and set subscript values and to encapsulate access to nested optionals on a single line, something that is far less convenient to accomplish (and error-prone) with forced unwrapping:

//  person is ordinary reference; residence is optional
//  member of person; address is optional member of residence
if let addy = person.residence?.address? {
     let name = addy.buildingName
     println("Got address: \(name)")
 }
 else {
     println("No address.")
 }

By encouraging use of optional unchaining, Apple also discourages reckless approaches that might leave code open to errors due to unexpected nil values; Apple is trying to get programmers to always handle nil values whenever they can occur (as one should). Of course, the errors generated by attempts to access members of a nil reference decrease productivity, as they are often pernicious and among the more difficult to debug. Furthermore, a given such error may manifest long after the offending code was merged into a larger codebase and after the developer best suited to track it down has disappeared.

What say you?