The following code compiles without warning:
var anything: Any
anything = "woof"
Makes sense... Any is any type
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.
It has its own history.
Starting with this feature, in the era of Swift 3.
SE-0116 Import Objective-C id as Swift Any type(So, called id-as-Any
in the history of Swift, which is the worst thing in the history, in my opinion.)
(Until then, Any
was not such a popular type in Swift programmers.)
It includes a fantastic new feature, universal bridging conversion, which guarantees literally Any
values in Swift can be convertible to non-null id
when bridging to Objective-C.
But this feature caused a huge number of disastrous tragedies, called _SwiftValue
.
In the original conversion first introduced in Swift 3, Optional<Double>
was converted to _SwiftValue
regardless of whether it was nil
or non-nil
. It is completely useless in Objective-C world, almost all operations on _SwiftValue
caused a crash, which could not have found till runtime, throwing away the goodness of strongly typed language.
So, Swift team needed to introduce a new feature immediately:
SE-0140 Warn when Optional converts to Any, and bridge Optional As Its Payload Or NSNull
Since this feature introduced in Swift 3.0.1, nil
s are bridged to NSNull
and non-nil
values are bridged based on the unwrapped values.
The feature also includes Warning when Optional
is converted to Any
to avoid unintentional misuse of Optional
to Any
.
The warning you have described started at this time in the Swift history.
The behavior of Optional
is backed by many compiler magics (so-called by Swift team members), for example, you cannot make use of simple if-let for other enum types, you cannot use Optional Chaining with your custom enums. Swift compiler treats Optional
as a special thing than normal enums.
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).
Here is how AnEnum
different from Optional
.
You can mimic Optional
by implementing everything Optional
does. However, what you create is another independent "Optional" that is not interoperable with Swift built-in Optional
. For example, you can implement ExpressibleByNilLiteral
so that you can assign nil
to AnEnum
which resolves to .first
. Just like how Optional
resolves nil
to Optional.none
enum AnEnum<T> : ExpressibleByNilLiteral {
case first
case second (T)
public init(nilLiteral: ())
{
self = .first
}
}
var anEnum: AnEnum<String> = nil
var anything = anEnum
However, this nil
will be DIFFERENT from the nil
of Optional
.
As suggested by the implementation, Optional
does not use the value of nil
directly, it just resolves nil
literal to Optional.none
which it actually stores.
Same as other Enum
, Optional.none
is just an unknown value (at least you couldn't guarantee it to equal any other values) since no rawValue
is specified.
Your nil
will be AnEnum.first
while Optional
's nil
will be Optional.none
. Optional.none
and AnEnum.first
are two different unrelated Enum values. Optional
won't consider AnEnum.first
as Optional.none
since they are different and the only way you get Optional.none
is by assigning nil
literal to Optional
.
This explains why you can assign AnEnum
, which you think is nearly identical to Swift native Optional
, to Any
with no problem but assigning an actual Swift native Optional
to Any
does causes problem. Because your custom "Optional" does not mean anything special other than an Enum
to the compiler. The compiler only treats Swift native Optional
specially even it is technically just an Enum
.
Because you can have an Optional<Any>
, by doing Any?
. The compiler has no idea if you are expecting your anything
type to be nil
or Optional.none
so it emits a warning to make sure you are aware of the situation.