Why non optional Any can hold nil?

SwiftOptional

Swift Problem Overview


In Swift I can declare a constant of type Any and put a String into it.

let any: Any = "hello world"

Good. On the other hand I cannot put a nil value into any because it's not optional.

let any: Any = nil

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

Perfect. But why does the compiler allow me do write the following code?

let couldBeNil: String? = nil
let any: Any = couldBeNil
print(any) // nil

Doesn't Any follow the Swift rule that only an Optional var/let can be populated with nil?

> Tested with Xcode Playground 7.2 + Swift 2.1.1

Swift Solutions


Solution 1 - Swift

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.

Solution 2 - Swift

From the Swift Docs: "Any: The protocol to which all types implicitly conform."

i.e. typealias Any = protocol<>

So when you declare String?, you could think of that as Optional<String>, where Optional<T> can be implemented as:

enum Optional<T> {
  case Some(T), None
}

And enums are Types, so they conform to the Any protocol. As stated in the comments, nil is not a type (and thus does not conform to Any, it is the absence of a value (and in this case no value has no type).

Optionals are baked into the language, and are not regular enums, but it still stands that they conform to Any while a typeless nil does not.

Solution 3 - Swift

For anybody looking for a way to check if Any contains nil:

If calling po value will tell you that an Any variable contains the value nil the following line still doesn't work:

if value == nil { ... } // Returns false and generates a warning

You have to convert it to a nillable value first before checking, for example when Any probably contains a Date:

if Optional<Date>.none == value as? Date { ... } // Returns true and no warning

The following line (thanks to @Shubham) doesn't work however:

if Optional<Any>.none == value as? Any { ... } // Does not compile

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLuca AngelettiView Question on Stackoverflow
Solution 1 - SwiftCristikView Answer on Stackoverflow
Solution 2 - SwiftAlex PopovView Answer on Stackoverflow
Solution 3 - SwiftLucas van DongenView Answer on Stackoverflow