Unwrapping Any, in Swift

If Any is not nil, except when it is, how do we check it and unwrap the underlying?

Unwrapping Any, in Swift
Photo by Simon Hurry / Unsplash

Did you know Any can be nil? It both makes sense and doesn't. But did you know Optional<Int>.none as Any == nil would return false? No? Me neither.

What else doesn't work then?

The issue with an optional Any

A basic direct equality comparison

let value: Any = Optional<Int>.none as Any
if value == nil {
    print("Value \(value) is nil")
} else {
    print("Value \(value) is not nil") // This branch is taken.
}

This check fails to recognize it as nil. This behaviour isn’t limited to Optional<Int>—any optional, like Optional<String>.none, behaves the same way when cast to Any.

Optional binding

let value: Any = Optional<Int>.none as Any
if let unwrapped = value as? Int {
    print("Value \(value) is not nil") // This branch is taken.
} else {
    print("Value \(value) is nil")
}

If case pattern matching

let value: Any = Optional<Int>.none as Any
if case nil = value {
    print("Value \(value) is nil")
} else {
    print("Value \(value) is not nil") // This branch is taken.
}

Switch pattern matching

let value: Any = Optional<Int>.none as Any
switch value {
case nil:
    print("Value \(value) is nil")
default:
    print("Value \(value) is not nil") // This branch is taken.
}

 If we try to match an Any value against nil in a switch or an if case statement, the pattern won’t match even when the underlying optional is .none.

Iterating over an array of heterogenous optional items

But it should be obvious, considering the first example

let values: [Any] = [Optional<Int>.none as Any, 42, "Swift"]

for (index, value) in values.enumerated() {
    // Even though value appears "nil" when printed,
    // the equality check with nil returns false.
    print(item)
    if value == nil {
        print("Value \(value) at index \(index) is nil")
    } else {
        print("Value \(value) at index \(index) is not nil")
    }
}

It will gladly print "Value nil at index 0 is not nil"

The reason, and the solution

Now, there are reasons for this behaviour. But if, like me, you need to check whether or not your Any is nil, they won't help you much. Luckily, we can still unwrap our optional Any or test for nil by comparing to Optional<Any>.none instead of nil. For example:

let value: Any = Optional<Int>.none as Any
switch value {
case Optional<Any>.none:
    print("Value \(value) is nil") // This branch is taken.
case Optional<Any>.some(let unwrapped):
    print("Value \(unwrapped) is not nil")
default:
    print("Value \(value) is not nil")
}

Courtesy of vacawama on Stack Overflow.