2

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.

oneWayTicket
  • 101
  • 6
  • 1
    Note that you haven't casted any varaible from `Any?` to `String` or `Int` only unwrapped them to `Any`. Still the result from calling `type(of:)` is weird, hopefully someone can explain. – Joakim Danielson Nov 27 '19 at 13:18

3 Answers3

3

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.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • thank you for clarification. So does this mean that swift has dynamism or some accessible facilities to observe type's metadata in runtime? Particularly, I would like to make a runtime function that checks all conformances and constraints and propagate errors that can be handled, instead of just crushing a programm, for example. – oneWayTicket Nov 27 '19 at 17:00
  • I don’t believe what you’re looking for is possible, but generally it shouldn’t be needed, and there shouldn’t be any need to crash the program either. Generally types should be known at compile time, so if you make a mistake, it just won’t compile. No need to check at runtime. And it shouldn’t be possible to crash. If you have a specific case you’re having trouble with, open a question with details, and I expect there’s a good answer. – Rob Napier Nov 29 '19 at 02:52
1

type(of: T) method gets runtime type of any variable. That is why you are seeing (type(of: au) as String. But Swift will not allow implicit type casting for safety reasons. That is the reason you can not add Int and Double without casting in Swift. You need to cast explicitly for your code to work.

Shreeram Bhat
  • 2,849
  • 2
  • 11
  • 19
1

type(of: T) Returns the dynamic type of a value.

You can use the type(of:) function to find the dynamic type of a value, particularly when the dynamic type is different from the static type. The static type of a value is the known, compile-time type of the value. The dynamic type of a value is the value's actual type at run-time, which can be a subtype of its concrete type.

This explanation is taken from comment above type(of: T) function. Do Cmd+click on type(of: T) in Xcode to read more

Siju
  • 510
  • 3
  • 10