Why Non Optional Any Can Hold Nil

Why non optional Any can hold nil?

TL;DR; Optionals in swift are translated by the compiler to Optional enum instances, and since Any can map to any value, it can be used to store optionals.


How does Swift represent optionals? It does it by mapping SomeType? to a concrete implementation of the Optional enum:

Int? => Optional<Int>
String? => Optional<String>

A simplified declaration of Optional looks like this:

enum Optional<T> {
case none // nil
case some(T) // non-nil
}

Now, a variable of type Any is able to hold an enum value (or any other kind of value, or even metatype information), so it should be able to hold for example a nil String, aka String?.none, aka Optional<String>.none.

Let's see what happens, though. As we see by the Optional declaration, nil corresponds to the .none enum case for all types:

nil == Optional<String>.none // true
nil == Optional<Int>.none // true
[Double]?.none == nil // also true

So theoretically, you should be able to assign nil to a variable declared as Any. Still, the compiler doesn't allow this.

But why doesn't the compiler let you assign nil to an Any variable? It's because it can't infer to which type to map the .none enum case. Optional is a generic enum, thus it needs something to fill the T generic parameter, and plain nil is too broad. Which .none value should it use? The one from Int, the one from String, another one?

This gives an error message supporting the above paragraph:

let nilAny: Any = nil // error: nil cannot initialize specified type 'Any' (aka 'protocol<>')

The following code works, and is equivalent to assigning a nil:

let nilAny: Any = Optional<Int>.none

, as the above Any variable is actually holding a valid value of the Optional enum.

Indirect assignments work too, as behind the scenes nil is converted to Optional<Type>.none.

var nilableBool: Bool? // nilableBool has the Optional<Bool>.none value
var nilBoolAsAny: Any = nilableBool // the compiler has all the needed type information from nilableBool

Unlike other languages, in Swift nil corresponds to a concrete value. But it needs a type to work with, for the compiler to know which Optional<T>.none it should allocate. We can think of the keyword as providing sugar syntax.

How does a non optional value obtain a nil value?

Consider something like this in Objective-C bridged to Swift:

- (NSObject * _Nonnull)someObject {
return nil;
}

The function is annotated as _Nonnull, but will return nil. This bridged as a non-optional object to Swift and will crash.

Swift : If variable a is non-optional then why variable b is an optional?

Variable b is an optional because variable a 'might' return nil and in such case b will also be nil.

Edit:
You have assigned an optional variable to another variable which you have not defined a specific type for. Xcode is saving you by creating the variable b as an optional. Consider this example:

var a: Int! = 6
a = nil
let b: Int = a
print(b)

Similar to yours, variable a here is an optional of type Int and b is a nonoptional of type Int too. Here I specifically define b to be a non optional integer unlike your b variable. Now, if i try to print variable who is assigned value of a which was at that point nil. This is dangerous and simply results in a fatal error at the assignment statement. The crash is simply understood because the compiler finds a nil value in a the optional type and tries to assign it to a non-optional.

Only optionals can hold nil value therefore when you assigned an optional(nillable) to another variable b, it is automatically treated as an optional because the value on which it relies is nillable and hence variable b is also nillable.

Swift nil Any != nil

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

Problem: My non optional variable is nil. (Fatal Error while unwrapping)

Its nil because your outlet is broken. Since you declared it as an implicitly unwrapped optional, balanceLabel.text = String(balanceValue) is the same as balanceLabel!.text = String(balanceValue). You can verify this is tru by simply using optional unwrapping (an implicitly unwrapped optional is still an optional): balanceLabel?.text = String(balanceValue). Go into your storyboard, right click the label and look at the connections. break any existing connections and then recreate the binding to your variable.

Best way to check non-optional values for nil in Swift

Even Apple's APIs sometimes return nil for a type that is not marked in the API as Optional. The solution is to assign to an Optional.

For example, for a while traitCollectionDidChange returned a UITraitCollection even though it could in fact be nil. You couldn't check it for nil because Swift won't let you check a non-Optional for nil.

The workaround was to assign the returned value immediately to a UITraitCollection? and check that for nil. That sort of thing should work for whatever your use case is as well (though your mail example is not a use case, because you're doing it wrong from the get-go).



Related Topics



Leave a reply



Submit