Learning Swift: Optional Types
Note: this post is part of a series about the Swift programming language, introduced at WWDC 2014. I’m no more experienced in Swift than anyone else outside Apple, but I learn best by coding and talking through a problem. If there’s a better way to approach some of these topics, get in touch on Twitter!
We’ve had a little over two weeks to play with the Swift programming
language now, and one sharp edge that keeps coming up is the
language’s inclusion of what they call “optional types”. The vast majority of
Objective-C developers are familiar with the use of nil
to indicate a
nonexistent value, but communicating that kind of information through a
variable’s type is a bit more foreign.
In this post, we’ll have an introductory discussion about how Swift provides optional types, go over a couple of implementation details, and point out a few tough spots in the optional system.
Types, Maybe?
Before we dive into code, let’s talk a bit about what it means for a type to be
optional. A lot of the variables we’ll encounter will have a “regular,”
non-optional type; these range from everyday value types (like Int
, Bool
, or
String
) to more complex class types (such as UIView
).
When we declare variables of these types, Swift requires that they always have a value. In fact, this requirement is so strict that attempting to use a variable before initializing it with a value is a compile error:
var x: Int
println(x) // Error: Variable 'x' used before being initialized
This may seem frustrating on the surface, but is actually incredibly helpful in
the long run: by preventing this code from compiling, Swift is eliminating an
entire class of potential runtime errors that would arise from using an
uninitialized value. In some cases, this requirement is even more strict – if
we were to use let
instead of var
, we’d find ourselves facing an error on
the declaration itself, rather than on the first call site:
let x: Int // Error: 'let' declarations require an initializer expression
Naturally, we can silence this error by providing a value for x
:
let x = 42
println(x) // prints "42"
However, astute developers will notice that we can’t provide x
a nil
value
at initialization time:
let x: Int = nil
This kind of code produces a rather opaque error about finding an overload for
__conversion
, but what it really means is simpler: we can’t provide a nil
value for variables of a non-optional type. Since x
is a plain Int
, it must
have an Int
value, which nil
is not.
This is where optional types come in. Swift lets you make virtually every type
optional by appending a question mark to the type name. For example, we can
shoehorn a nil
value into our x
from earlier just by tacking that ?
onto
the Int
type in the declaration:
var x: Int? = nil
println(x) // prints "nil"
x = 42
println(x) // prints "42"
At this point, we’ve come around to what most developers would expect in
Objective-C: object variables can have a “real” value or nil
, and it’s just up
to you to check which case your code is handling.
Little Boxes
“But wait,” you say, “Int is a value type, not an object! How can I use nil
for a value? There was no such thing for NSInteger…”
Well, you’re right. NSInteger didn’t have a nil
value (or, rather, using nil
with the right coercion would get you an integer with a value of 0). Instead, we
defined a ton of marker values that meant “no value:” 0
, 1
, NSIntegerMin
,
NSIntegerMax
, and NSNotFound
all mean “nothing” in some API.
When you stop to think about it, this is really a limitation: by not having a
consistent, defined way of saying “no integer value,” we’re layering a tiny bit
of additional complexity around any use of such a value, then attempting to
paper over it with documentation. Want to find an object in an array? Well, if
that object doesn’t exist, you get NSNotFound
– but if you try to find a
nonexistent row in a table view, you get -1
instead.
Swift provides a cleaner way around this problem with the kind of optional types we describe above. How can it work with any type, though? Well, under the hood, an optional type is, well, just a type:
enum Optional<T> {
case None
case Some(T)
}
The above is the actual definition of Optional
in the Swift core library
(slightly abridged for clarity). Swift defines a new type called Optional that
always has exactly one of two values: a defined “nothing” value called None
,
or a wrapped-up value of some other type T
. It’s as if Swift can take regular
values and place them inside a box, which may or may not be empty:
In this example, the first integer is a plain Int
type. The second and third,
though, are both of type Optional<Int>
– or, for short, Int?
. Notice that
the third value here is actually an “empty box” (the None
value), even though
its type is Int?
.
This ability, to pass around None
anywhere an optional type can go, is how
Swift can provide things like nil
for value types like Int
(or, for that
matter, any type, whether value or reference). Since this value will have the
same type as a “real” value wrapped up in Optional
, they can both be
represented in the same variable without trying to rely on special values to
stand in for the concept of “no value.”
Unwrapping
This poses a problem of its own, though. Now that we know optionals are their own type, we realize that they can’t be passed anywhere their underlying type can:
func double(x: Int) -> Int {
return 2 * x
}
let y: Int? = .Some(42)
println(double(y)) // error: Value of optional type 'Int?' not unwrapped
We need some way of getting at the value inside an optional’s box – and, for
that matter, checking whether such a value exists at all! Thankfully, Swift has
us covered with the !
operator, which extracts the value out of an optional:
let y: Int? = .Some(42)
println(double(y!)) // prints '84'
This works great for optionals that have a value. But what about those that don’t?
let y: Int? = nil // same as Optional.None
println(double(y!)) // runtime error: Can't unwrap Optional.None
The !
operator only applies to optionals that have an actual value inside
them. If your optional has nil
(an alias for .None
), it can’t be unwrapped
and will throw a runtime error.
Let’s make our code a bit smarter. Instead of unconditionally unwrapping our
optional value, we can check whether the value is nil
first – much like we
might have done in Objective-C.
let y: Int? = nil
if y {
println(double(y!))
} else {
println("No value to double!") // prints "No value to double!"
}
But what if we got y
from another method altogether, instead of defining it
locally? It would feel a bit verbose to require a new let
or var
statement
wherever we intend to call a func
, then immediately check that just-declared
variable.
Swift has us covered here too, with a syntax called optional binding. By
combining an if
and a let
statement, we can write a concise one-line check
for a newly-bound variable that is only conjured into existence if there’s a
real value to go along with it:
if let y: Int? = someObject.someInt() {
println(double(y))
} else {
println("No value to double!") // prints "No value to double!"
}
You may have noticed that here, we don’t even need to explicitly unbox y
using
the !
operator. This is another convenience we get for free by using optional
binding: the bound variable is of the underlying type (in this case Int
),
instead of keeping the wrapping Optional
type. Since we’re sure such a value
exists, we can access it directly inside the body of the if
statement, rather
than having to unwrap it by hand.
Chaining
Now we’ve built up a concise syntax for checking and unwrapping optional
variables. But what about calling methods on those variables? Your program might
have some custom classes – most do, after all – and you could want to call a
method on an variable that might be an instance, or might be nil
. In
Objective-C, trying the latter would just give you another nil
-like value
right back.
Thankfully, Swift anticipated this case too. Developers can make use of
optional chaining to call methods on potentially-nil
objects:
let y: SomeClass? = nil
let z = y?.someMethod() // will produce nil
By sticking a ?
between the variable name and method call, we can indicate
that we want either a real answer back (in the event that y
is a valid
instance) or another nil
(in the case that y
is itself nil
). This should
feel very familiar to Objective-C pros: it’s exactly what that language would do
in this situation.
The one caveat: the type of the returned value will always be optional, even
if the method itself declares a non-optional return type. Since the value being
computed could become nil
at any point along the chain (if the object
being called is nil
), the return value has to be prepared for that
possibility, and the only type we have in Swift capable of carrying a nil
value is an optional. Consider:
class SomeClass {
func someMethod() -> Int {
return 42
}
}
let y: SomeClass? = nil
let z = y?.someMethod() // of type Optional<Int> with value nil
Even though someMethod()
is declared to return an Int
, z
gets type
Optional<Int>
because we used optional chaining to call the method.
This might seem like a hassle, but can actually be helpful, especially when combined with optional binding from above. If we stick with the same class definition, we can try something like this:
let y: SomeClass? = nil
if let z = y?.someMethod() {
println(double(z))
} else {
println("Couldn't get value from someMethod()")
}
This remains concise while still dealing with all the various concerns we might have:
- If
y
isnil
(as it is here), the optional chaining will still allow us to write this code without a type error - If
y
isnil
orsomeMethod()
returnsnil
, the optional binding will catch that case and avoid giving us anil
value for non-optionalz
- In the event we do get a
z
, we’re not required to hand-unwrap it because it’s optionally bound
All in all, this is a pretty clean system for passing around nil
values for
just about any type. We get some extra type safety out of the deal, avoid using
specially defined values, and can still be just as concise as Objective-C – if
not more!
Rough Edges
That’s not to say the optional system isn’t without its quirks, though. A few rough edges in early versions of Swift can lead unwary developers into unexpected situations.
Unary ?
operator
It’s (currently) valid Swift to take an optional variable and throw a ?
at the
end. However, unlike the unwrapping operator !
, appending ?
doesn’t actually
affect the variable in any way: it is still optional, and surrounding if
checks will still look to see if the variable is nil
, rather than evaluating
its contained truth value (if any).
This can cause extra trouble when combined with…
Optional<Bool>
Since the Optional type is defined using generics – that is, it can wrap any
other type in the language – it’s possible to construct an optional boolean
variable. In fact, it’s virtually mandatory the language allow this: to
special-case Bool
to disallow optionals would be an exceptional change,
requiring serious modifications to the language or the Optional type.
That does, however, lead to a way developers can construct a kind of three-state
variable: an Optional<Bool>
can be true
, false
, or nil
. (What the latter
means is rather context-dependent.) This can be very misleading, though, when
combined with an if
check:
let x: Optional<Bool> = .Some(false)
if x {
println("true")
} else {
println("false")
}
// prints "true"
Since the if
in this case checks whether x
is nil
, not the underlying
truth value of x!
, this code snippet prints “true”. Even worse, it’s possible
to write the same code with a tiny tweak:
let x: Optional<Bool> = .Some(false)
if x? {
println("true")
} else {
println("false")
}
// prints "true"
As discussed above, the unary ?
operator has no effect in this context – what
looks like an unwrapped optional boolean is actually still optional, leading the
snippet to continue printing “true.” (To really unwrap x
, we need to use the
!
operator.)
Implicit implicit unwrapping
Interface Builder is an important component of most iOS and Mac apps, and as Xcode continues to develop new features, it will only grow more so. However, not everything is possible in IB; developers will often hook up IBOutlets to gain programmatic control over different UI elements.
In Swift, the annotation to expose a variable to IB is nearly identical to its Objective-C counterpart:
class MyViewController: UIViewController {
@IBOutlet var myView: UIView
}
This will expose myView
to be hooked up in Interface Builder. At this point,
what’s the type of myView
? UIView
, right?
Wrong. In Swift, marking a variable with @IBOutlet
implicitly turns that
variable into an implicitly unwrapped optional, even if its declared type
doesn’t include the !
annotation. What’s worse, letting Xcode create this
variable declaration – perhaps by control-dragging from a .xib or storyboard –
will write it as shown above, without the extra !
after the type. To be
extra-correct, we should instead write:
class MyViewController: UIViewController {
@IBOutlet var myView: UIView!
}
Note that this quirk is also virtually required: since IBOutlets don’t strictly
need to be hooked up in a .xib or storyboard, it’s always possible that an
outlet will wind up with a nil
value at runtime. Using implicitly unwrapped
optionals allows for this case without mandating significant changes to existing
codebases or requiring use of optional binding and chaining everywhere IB comes
into play.
Necessary evils
Most of these problems wind up being required by the language’s core tenets, or by Objective-C compatibility, as described above. However, developers still need to balance the good with the bad when adopting Swift – and to keep in mind that the language is still in flux. Future changes may yet obviate several of these quirks; file radars to encourage Apple to fix your favorite bug!