Reputation: 4462
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.
Any
, how might one discover that it is, in fact, nil?Upvotes: 1
Views: 1326
Reputation: 63157
Think of Any
and Optional<Wrapped>
like boxes.
Any
is an opaque box that can hold anything. You can gleam what's inside by trying to cast it using as?
.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.
x
is a value of Optional<Any>.none
, a.k.a. nil
. nil
gets printed, so that makes sense.
type(of: x)
is the type of x
, Optional<Any>
, as we expected.
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.
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.
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>
.
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
tonil
always returnsfalse
Upvotes: 6
Reputation: 17844
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.
Upvotes: 1