1

Swift 5.1 . Consider the following.

let x: Any? = nil
let y: Any = x
print("x \(x)")                      // x nil
print("type(of: x) \(type(of: x))")  // type(of: x) Optional<Any>
print("x == nil \(x == nil)")        // x == nil true
print("y \(y)")                      // y nil
print("type(of: y) \(type(of: y))")  // type(of: y) Optional<Any>
print("y == nil \(y == nil)")        // y == nil false

We have two variables, set equal to the same thing - nil. They print the same, their type prints the same - but one == nil and the other does not.

  1. Why?
  2. Given such an Any, how might one discover that it is, in fact, nil?
Erhannis
  • 4,256
  • 4
  • 34
  • 48

2 Answers2

6

Think of Any and Optional<Wrapped> like boxes.

  1. Any is an opaque box that can hold anything. You can gleam what's inside by trying to cast it using as?.
  2. Optional<Wrapped> is a transparent box. It lets you see that its contents are either a value of Optional<Wrapped>.some(wrapped) or Optional<Wrapped>.none().

An Optional<Any> and Any containing an Optional<Wrapped> aren't the same thing.

1. x

is a value of Optional<Any>.none, a.k.a. nil. nil gets printed, so that makes sense.

2. type(of: x)

is the type of x, Optional<Any>, as we expected.

3. x == nil

is calling an == operator of type (T, T) -> Bool. Since both args need to be the same type, and since x has a value of Optional<Any>.none (of type Optional<Any>), nil is inferred to be Optional<Any>.none. The two are equivalent, so we get true, as expected.

4. y

is a value of type Any. At compile time, nothing more is known about y. It just so happens that the value behind hidden by the opaque curtain of Any is x. print is implemented to "see through" Any instances, to show you what's really there.

5. type(of: y)

is doing something similar to print. It's using runtime type information to see exactly what the runtime value stored into y is. The static type of y is Any, but type(of: y) shows us that the runtime type of its value is Optional<Any>.

6. y == nil

This is where it gets a little trickier.

The choice of which overload of a function or operator to call is done at compile time, based on the statically known types of the operands. That is to say, operators and functions aren't dynamically dispatched based on the types of their arguments, the way they're dynamically dispatched based on the type of their objects (e.g. the foo in foo.bar()).

The overload that's selected here is ==(lhs: Wrapped?, rhs: _OptionalNilComparisonType) -> Bool. You can see its implementation on lines 449-481 of Optional.swift.

Its left-hand-side operand is Wrapped?, a.k.a. an Optional<Wrapped>.

Its right-hand-side operand is _OptionalNilComparisonType. This is a special type that conforms to ExpressibleByNilLiteral.

Optional is one type that conforms to ExpressibleByNilLiteral, meaning that you can write let x: Optional<Int> = nil, and the compiler could use that protocol to understand that nil should mean Optional<Int>.none. The ExpressibleByNilLiteral protocol exists to allow this behaviour to be extensible by other types. For example, you can imagine a Python interopability framework, where you would want to be able to say let none: PyObject = nil, which could be passed into Python and resolve to None, Python's null type.

What's happening here is that the left hand side (y, of type Any), is being promoted to type Any?. The promoted value has type Optional<Any>.some(old_value_of_y). The right hand side, the nil, is being used to instantiate a value of _OptionalNilComparisonType, equivalent to calling _OptionalNilComparisonType.init(nilLiteral: ()).

Thus, your call site is equivalent to:

Optional<Any>.some((Optional<Any>.none) as Any) == _OptionalNilComparisonType(nilLiteral: ()) /*
↑                  ↑↖︎_the value of x_↗︎       ↑↑
↑                  ↖︎_the value of y _________↗︎↑
↖︎_the value of y after promotion to optional__↗︎ */

According to the implementation, the left side is a some, the right side is a nil, and thus they're unequal.

You might say:

Well this is pretty non-obvious behaviour, it's not what I wanted.

To which I would respond:

Don't ignore the compiler errors, they're right on point:

warning: comparing non-optional value of type Any to nil always returns false

Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Well, this is a good lead - now, when you say `Optional` is not the same as an `Any` containing an `Optional`, do you mean according to types at compile-time, according to types as run-time, or something else? – Erhannis Apr 30 '20 at 18:57
  • At compile time, `Optional` and `Any` are two different types, but Swift has special rules that kick into play when you try to make them inter-operate. You could pass an `Any` where an `Optional` is expected, causing optional promotion. It's the same thing happens when you have `func foo(_: Int?) {}` and call `foo(123)`. The value `123` of type `Int` is promoted to the value `Optional.some(123)` of type `Optional`. – Alexander Apr 30 '20 at 19:13
  • You can also try to do the opposite, passing an `Optional` where an `Any` is expected. You might see this if you try `print(Optional.some(123))`, getting the warning: "Expression implicitly coerced from `Optional` to `Any`. The compiler is warning you that you're turning an optional into a non-optional, by virtue of obscuring its optionality behind the `Any` wrapper. The type of the parameter inside `print` has type `Any`, but the value behind stored in that opaque box is still that `Optional.some(123)`. – Alexander Apr 30 '20 at 19:15
  • Ohhh, I see. `y == nil` effectively wraps `y` in another layer of optional, making its runtime type `Any??` (as received by the `==`). The first layer is `some`, containing an optional of `none`. As much as I might want `==` to e.g. check the runtime type of `Any`s, your explanation explains all the evidence. Thanks! – Erhannis Apr 30 '20 at 19:31
  • Yep, you're spot on. Technically, I think `==` could have been implemented to take two `Any` instances, any do all the comparison using runtime data. But the performance cost of that would be very severe, and all to solve a problem that's caught statically and warned by the compiler – Alexander Apr 30 '20 at 19:44
1

In Swift 5.2, the compiler generates warnings that explain what is going on:

let y: Any = x // "Expression implicitly coerced from 'Any?' to 'Any'"
print("y == nil \(y == nil)")  // "Comparing non-optional value of type 'Any' to 'nil' always returns false"

Given such an Any, how might one discover that it is, in fact, nil?

Non-optionals are never nil.

Gereon
  • 17,258
  • 4
  • 42
  • 73
  • Ok, except...it is. It is nil. I set it to nil, and printing its value confirms that it is still, in fact, nil. What value do you claim that it is, if it is not nil? – Erhannis Apr 30 '20 at 18:40
  • What do you mean? `Any` can be any reference/value type. `nil` is neither, so `Any` cannot hold `nil`, but `Any?` can. – Renzo Tissoni Apr 30 '20 at 18:45
  • @RenzoTissoni Please, then, explain the first two lines of my code, where I set `x: Any? = nil`, and `y: Any = x`, and further down where `print("y \(y)")` prints `y nil`, despite that `y` is declared to be type `Any`. The behavior of the compiler and code strongly suggests that a variable of type `Any` is containing a value of `nil`. Is there some trickery the code is performing that obscures what is actually happening? – Erhannis Apr 30 '20 at 18:53
  • Check the answer to this question: https://stackoverflow.com/questions/34644128/why-non-optional-any-can-hold-nil – Renzo Tissoni Apr 30 '20 at 19:03
  • 1
    @RenzoTissoni I don't understand what you mean by "Any cannot hold nil." `Int?` is a subtype of `Any`, and the value of an `Int?` can be nil, so `Any` can be nil. `Any?` is also a subtype of `Any`. The answer you link says that pretty explicitly. `nil` is a value. It's just an alias for `Optional.none` (an enum value). – Rob Napier Apr 30 '20 at 19:13
  • One thing I have learned today is that apparently, `nil` doesn't exist, so nothing is EVER `nil`; they're merely set to specific flavors of `.none`. OR, conversely, all `.none` ARE `nil`, in which case `Any` CAN also be `nil` (and frankly this is a more practical definition, IMO), BUT there are some weird caveats about the behavior of `nil` that you occasionally have to be aware of. – Erhannis Apr 30 '20 at 19:34
  • @RobNapier a more accurate statement would've been that `Any` cannot be initialised to `nil`. – Renzo Tissoni Apr 30 '20 at 22:17
  • @Erhannis `nil` does exist; it's just syntactic sugar for `.none`. I don't think there are any places that you can write `nil` that you cannot write `.none` and vice-versa. And `.none` is just a normal value. (Optionals do have some magic and some sugar, but they're still just values.) But you're correct that there is no `nil` absent a type. It's generic, just like Array. So perhaps you mean "nil is not a single value like null is in other languages." – Rob Napier Apr 30 '20 at 22:23
  • 1
    @RenzoTissoni Sure it can. `let x: Any = nil as Any?`. You can't say `let x: Any = nil` just because the compiler doesn't know which kind of `nil` you want. You can also write `let x: Any = nil as Int?` (though that'll generate a warning). Strangely, you can write `let x: Any = []` and it'll infer `[Any]`, so in principle `let x: Any = nil` could work, inferring `Any?`, but you would never want to use it (because `Any?` is a cursed type). – Rob Napier Apr 30 '20 at 22:25