Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't you assign an Optional to a variable of type `Any` without a warning?

Tags:

swift

optional

The following code compiles without warning:

Version 1:

var anything: Any
anything = "woof"

Makes sense... Any is any type, value type or reference type.

However, if we create an optional variable like a Double?, this code throws a warning:

Version 2:

var anything: Any
let aDouble: Double? = 3
anything =  aDouble

But this code does not throw a warning:

Version 3:

enum AnEnum<T>: {
    case first
    case second (T)
}

var anEnum: AnEnum<String> = .first
anything = anEnum



You can rationalize that version 2 throws a warning because Any is not an Optional type, and Double? is an Optional type. Trying to assign an Optional to a non-optional type is sort of a type mismatch.

However, under the covers, an Optional is an enum with a .none case, and with a .some case, where the .some case has an associated value. My version 3 uses an enum, AnEnum, that also has 2 cases, the second of which has an associated value. The AnEnum type is nearly identical to the Swift native Optional type.

Why is assigning an AnEnum value to anything ok, but assigning an Optional value to anything is not ok?

(I started to answer this question: Swift dictionary with mix types (optional and non-optional))

And then realized that I didn't really know the answer.

like image 553
Duncan C Avatar asked Sep 26 '18 00:09

Duncan C


People also ask

How do you declare an optional variable?

How to declare an Optional? You can simply represent a Data type as Optional by appending ! or ? to the Type . If an optional contains a value in it, it returns value as Optional<Value> , if not it returns nil .

Can any be optional Swift?

Any data type can be optional in Swift: An integer might be 0, -1, 500, or any other range of numbers. An optional integer might be all the regular integer values, but also might be nil – it might not exist.

How do I declare optional type in Swift?

Optional Types Syntax and Usage in Swift Here's the syntax for an optional type: <data-item> <var-name>:<data-type>? The declaration is similar to declaring regular variables, except that you add a question mark (?) beside the data type which makes it an Optional type.

What is optional type in Swift?

An Optional is a type on its own, actually one of Swift 4's new super-powered enums. It has two possible values, None and Some(T), where T is an associated value of the correct data type available in Swift 4.


2 Answers

Quite simply, it's because Any is like a Roach Motel for Optionals.

The Roach Motel is a cockroach trap whose motto is, "Roaches check in, but they don't check out." The same is true for Any and Optionals. You can put an Optional into an Any, but you can never get it out again.

To see what I mean, let's first put something else into an Any and get it out again:

let s : String = "howdy"
let any : Any = s
let s2 = any as! String
s2 // "howdy"

Now let's try that with an Optional:

let s : String? = "howdy"
let any : Any = s
let s2 = any as! String? // error

Ooops! You can't cast a nonOptional "down" to an Optional, so the original Optional is lost.

The thing wrapped by the Optional is not lost. You can still unwrap it:

let s : String? = "howdy"
let any : Any = s
let s2 = any as! String
s2 // "howdy"

But now s2 is a String, not an Optional. The Optional is gone for good. You can't get it out. You can't find out that what was put into the Any was an Optional. It's gone.

So that's why putting an Optional into an Any always elicits a warning. The compiler is saying: "You can do this, but do you really understand what you're doing?" And if you do, there are ways to silence the warning (and the error message tells you what they are).

like image 144
matt Avatar answered Oct 10 '22 23:10

matt


Optional is more than just an enum. It's also an implicit promotion of every type. Any and Optional combine in weird ways that can be surprising. For example, T? is Any. T?? is also Any. Any is Any, Any? is Any. Any?? is also Any.

This creates a number of headaches, and subtle up-casts and down-casts make it very unclear what the optionality of an Any really is. For example.

var anything: Any = ""
let aDouble: Double? = 3
anything = aDouble as Any
let x = anything as? Double

What's x here? Well, aDouble is Optional<Double>, not Double, so as? Double should return nil. But it doesn't because magic. It returns Optional(3). Which is frankly kind of weird (try to get the same thing with a version of MyOptional as an enum), but it would be really bad the other way. Consider:

let data: [String: Any] = ["aDouble": aDouble]
let y = data["aDouble"] as? Double

y is Double? here, not Double?? because of optional coalescing. And thank goodness. But it's bizarre. And if you had generics involved with conformances based on Optional, this could get really confusing (and does; so many questions come up about that....) Now replace Double above with Any and it goes Inception on you really fast.

So the compiler is warning you that mixing Any with Optional is a bad idea and can lead to weird type results, and you definitely should be calling it out anytime you do it. (And maybe you should stop using Any because it is almost never the type you really mean anyway.)

To make it the most concrete example I can think of, consider:

var anything: Any = ""
let one: Int = 1
let maybeTwo: Int? = 2

anything = one
anything as? Int // fine

anything = maybeTwo
anything as? Int? // illegal (see below)
anything as? Int  // fine (but why? that's not what we put in.)

You'd think this would be parallel, but it isn't. You can't just pull out the type you put in when you throw Optionals into the mix.

BTW, interesting addendum, you can't downcast from Any to an Optional type.

let x = anything as? Double? // Illegal

That's really interesting, because anything is a Double?:

(lldb) p anything
(Any) $R4 = {
  payload_data_0 = 0x4008000000000000
  payload_data_1 = 0x0000000000000000
  payload_data_2 = 0x0000000000000000
  instance_type = Double?
}

So how can I tell the difference between "anything is Double? but nil" vs "anything is String"? You'd think you could test that, but I don't think you can, because none of this works consistently. Instead it tries to be convenient. But you can't push it too hard or all the seams will show.

like image 40
Rob Napier Avatar answered Oct 10 '22 21:10

Rob Napier