Any should never be nil

Consider this code snippet

var foo: String? = nil
let bar = foo as Any
print(bar) // -> nil
print(bar == nil) // -> false

This feels like it doesn't make much sense. bar is not optional in this case, and thus should never be nil, but it can be nil. Because bar is not optional, you cannot utilize any of the unwrapping methods Swift provides (guard, if let, ??, !)

I understand the reasoning behind this, but it doesn't feel like it fits with the rest of Swift's type system and only works this way on a technicality.

In my opinion, either variables should need to be Any? to be nil, or unwrapping methods should be enabled for variables of type Any.

Optional is special, but it isn't that special. If Any can't be nil, then the whole type system falls down. For example:

extension Sequence {
  func firstAsAny(where predicate: (Element) -> Bool) -> Any {
    return self.first(where: predicate)! as Any
  }
}

let arrayOfOptionals: [Int?] = [1, nil, 3]
let match = arrayOfOptionals.firstAsAny(where: { $0 == nil }) // as Any
print(match) // nil
print(match == nil) // false

And really, you have the same problem with nested optionals as well:

let foo: String? = nil
let bar: String?? = foo
print(bar) // nil
print(bar == nil) // false

Yes, these are tricky types to work with, so most of the time you should avoid it. But sometimes it's necessary and sensible.

1 Like

I think its fine that Any can be nil - but why does it support "=="?

1 Like

Yeah that's the part that confuses me a bit. nil doesn't make sense without context about which particular type is nil.

// nil is obviously Optional<String>.none
let string: String? = nil

// what in the world is nil here? Optional<Void>.none?
let any: Any = nil

It makes sense to be able to compare Any.Type with == but it seems like there should not be an overload for using == with Any on both sides.

It's a compiler error:

t2a.swift:1:16: error: nil cannot initialize specified type 'Any'
let any: Any = nil
               ^
t2a.swift:1:10: note: add '?' to form the optional type 'Any?'
let any: Any = nil
         ^
         (  )?

I get a

Comparing non-optional value of type 'Any' to nil always returns false

warning on print(bar == nil)

@Douglas_Gregor yeah sorry I should have been more clear with that. I meant in terms of this line:

let bar: Any
print(bar == nil) 

Isn't the usage of nil there similar to doing nil as Any or let any: Any = nil?

(Or maybe == just accepts Any? on the right hand side which would make sense...)

It's turning bar into an Any? and checking whether it's nil. The == being used is defined here. The intent is to allow one to use == nil or != nil to check whether a value of type T? contains a value, when T does not conform to Equatable.

Maybe we should have simply required developers to use a different "test for nil" expression.

Doug

5 Likes

Yeah, probably something like Optional.isNil would nicer... but on the other hand, veterans are already used to nil not being equal to nil ;-)

var foo: String? = nil
let bar = foo as Any
print(bar) // -> nil
print(bar == nil) // -> false
...
print(bar as! Optional<Any>) // nil
print(bar as! Optional<Any> == nil) // true
1 Like

This is an interesting one! I suspect this is due to Optional's special subtyping rules, which have T?: U? whenever T: U. So the as! cast is upcasting from the runtime type String? to Any?, rather than from Any to Any?. If we use an as cast instead we get false:

print(bar as Optional<Any> == nil) // false
2 Likes