Search code examples
swiftanyforceunwrapswift-optionals

Any? incorect semantics


I was playing around with some code in swift and encountered one interesting case. Lets start with a little preamble: suppose you create some optional variables:

let a: String? = "abcd"; let b: Int? = 4
print(
    "Type of \(a) is \(type(of: a))" //Type of Optional("abcd") is Optional<String>
    "Type of \(b) is \(type(of: b))", //Type of Optional(4) is Optional<Int>
    separator: "\n"
)

Then you force unwrap so types of au and bu are not optional.

let au = a!; let bu = b!
print(
    "Type of \(au) is \(type(of: au))", //Type of abcd is String
    "Type of \(bu) is \(type(of: bu))", //Type of 4 is Int

    au + String(bu), //abcd4
    separator: "\n"
)

Seem reasonable, but things start to go weird, when you try to apply same code to Optional<Any>:

let a: Any? = "abcd"; let b: Any? = 4
let au = a!; let bu = b!
print(
    "Type of \(a) is \(type(of: a))", //Type of Optional("abcd") is Optional<Any>
    "Type of \(b) is \(type(of: b))", //Type of Optional(4) is Optional<Any>
    "Type of \(au) is \(type(of: au))", //Type of abcd is String
    "Type of \(bu) is \(type(of: bu))", //Type of 4 is Int
    //au + String(bu),
    separator: "\n"
)

But now if you try to to do same concatenation au + String(bu), swift will produce compilation error, even though these two variables are known to be of some concrete type, as reported by swift itself. The error is:

error: protocol type 'Any' cannot conform to 'LosslessStringConvertible' because only concrete types can conform to protocols

This certainly looks like a bug, isn't it. Please, share your opinion.


Solution

  • As others have noted, type(of:) is the dynamic, runtime type of a value. The compiler relies only on the static, provable type of a value. In the code above, the only thing that the compiler can prove is that au will be of type Any. One of the many types that conform to Any is String, but the compiler doesn't know that in all possible code paths, the value really will be a String.

    Since there is no func + (Any, String) overload, this can't compile.

    Note that Any? is a bizarre and dangerous type. The problems with it aren't the cause of this example, but the way that it interacts with Optional promotion can lead to significant ambiguity and confusion. Every type in Swift can be implicitly promoted to an Optional of that type. And every type in Swift can be implicitly cast to Any. Combining those two facts means that Any can be implicitly promoted to Any?, or Any??, or Any???, etc., and yet all of those are also subtypes of Any. This can create all kinds of subtle headaches. You should strongly avoid using Any; it is very seldom the right tool. But you should even more careful of allowing Any? to show up in your code. It is an indication that something has probably gone wrong.