Gotcha: Comparison Operators and Optional Chaining in Swift

5/19/16

I’ve been using Swift for about a year and a half now, and mostly I am a believer. I like it and find myself advocating Apple’s new programming language when I talk to other developers. However, there are, infrequently, a few issues. I just ran into a Gotcha today and wanted to write about it.

Here’s a piece of code, or near enough, that cropped up in one of my programs:

if self.instance!.value < 0 {
    // other code ...
}

Due to a bug in my program, instance was nil which caused the program to crash. So, I thought, let’s make use of some of this optional chaining in Swift. I changed the above code to:

if self.instance?.value < 0 {
    // other code ...
}

I then thought to myself, before I commit to this style, I should understand more of the meaning of this kind of expression. I’m familiar with how Swift behaves in an if let = ... context, but wasn’t sure about a combination of optional chaining with a comparison operator  (i.e., < 0). So, I loaded a Playground in Xcode, and did some tests:

Here’s what I came up with when instance was nil:

instance?.value > 0 // Playground: false
instance?.value < 0 // Playground: true: YIKES!
instance?.value == 0  // Playground: false

Perhaps even more interesting, this behavior is not limited to comparisons against 0:

instance?.value < -1 // Playground: true: MORE YIKES!
instance?.value < 1 // Playground: true: TRIPLE YIKES!

So, when I write:

if self.instance?.value < 0 {
    print("Gotcha!")
}

I’ll get the “Gotcha!” output a) when self.instance is non-nil and its value member is negative, and b) when self.instance is nil. On the surface, this seems to miss the point of “failing” the optional chain when the object is nil. What I was originally wanting from this expression was that the test self.instance?.value < 0 would always be false when self.instance was nil.

What causes this Gotcha behavior?
This language behavior stems from two characteristics currently in Swift:
1) optional chaining such as instance?.value results in a value of nil if the instance is nil, and
2) nil relational comparisons (e.g., nil < 0, nil > 0, nil == 0) are apparently computed on the basis of nil, or rather optionals, as generally being Comparable (also see these SO links:  SO1 and SO2 and SO3).

So, when we have instance?.value < 0, and instance is nil, this evaluates to nil < 0 and then true.

Any reasonable workarounds?
We have to first define our objectives. I started this off by saying I didn’t like the style of:

if self.instance?.value < 0 {
    // other code ...
}

because it will be true when instance is nil (and when instance is not nil, but the value is less than 0). For me, this seems difficult to reason about, and consequently prone to bugs. What I’d really like is a generally failable conditional expression in the if-else structure. So that if any part of a conditional with optional chaining fails, then the entire expression fails, and returns false.

To me, this seems consistent with the behavior of optional chaining in other contexts, such as:

john.residence?.address = createAddress()

In the above statement, when residence evaluates to nil, the statement on the right hand side fails, and is not executed (see Apple’s reference on optional chaining).

And:

if let x = instance?.value {
}

which has a conditional value of false when the optional chaining fails.

It seems, however, that a generally failable conditional expression requires compiler support. For example, with:

func evaluate(x: Int) {
    if instance?.value < 0 || x > 10 {
        print("Hurrah!")
    }
}

what I’d like to see is that “Hurrah!” is *never* printed when instance was nil– i.e., I’d like the entire conditional expression to fail (evaluate to false) if any optional chaining within it failed.

As a partial solution to this issue, I’m thinking about using new operators. For example:

operators

For the text of this swift code, see this link.

This would give, for example:

if nil <? 0 {
    print("Yes")
}
else {
    print("No")
}

as always printing out “No”.

And coming back to my original code:

if self.instance?.value <? 0 {
    // other code ...
}

would execute the “other code” only when instance was not nil, and the value property was negative.

About the author: Christopher G. Prince has his B.Sc. in computer science (University of Victoria, B.C., Canada), an M.A. in animal psychology (University of Hawaii, Manoa), an M.S. in computer science (University of Louisiana, Lafayette, USA), and a Ph.D. in computer science (University of Louisiana, Lafayette, USA). His M.S. and Ph.D., while officially in computer science, were unofficially in cognitive science, split between animal psychology and computer science. Chris is a dedicated animal person, and has also developed: Catsy Caty Toy, a customizable and shareable iPhone and iPad app for your cats (http://GetCatsy.com)Petunia, an app for recording and sharing pet health information (http://GetPetunia.com), and WhatDidILike, an iPhone app to keep track of restaurants and food that you like (http://WhatDidILike.com).

Creative Commons License
“Gotcha: Comparison Operators and Optional Chaining in Swift” by Christopher G. Prince is licensed under a Creative Commons Attribution 4.0 International License. Permissions beyond the scope of this license may be available at chris@SpasticMuffin.biz.