Printing the Static Type of a Value

I was recently trying to figure out how to print the static type of a value. My initial reaction was just to print the result of passing the value to type(of:). This does not return the intended value though because as stated in its documentation, type(of:) "returns the dynamic type of a value." After playing around with things for a bit longer, I stumbled upon a solution, but I don't totally understand why it works:

enum Foo: Error {
    case bar
}
let error = Foo.bar as Error

func staticType<T>(of value: T) -> String {
    return "\(type(of: value))"
}

print(type(of: error))
// Prints "Foo"

print(staticType(of: error))
// Prints "Error"

I found this solution I stumbled upon sort of confusing and as such I have a few questions.

Are there any other ways to print the static type of a value? Why does staticType work as it does and not print the dynamic type of error even though it uses type(of:) at its core? Why does wrapping the call to type(of:) in a function produce a different result? What is the difference between the two print calls?

3 Likes

Your staticType still isn't quite right. What you want is:

func staticType<T>(of _: T) -> String {
    return "\(T.self)"
}

because type(of: value) in a generic context would still return the dynamic subclass of an object instance you pass in.

There are really two variants of type(of:). When you apply it to something known to be a protocol type, it opens the protocol type and gives you the type of the value inside as a protocol metatype. For cases where we don't see that a type is a protocol type, we can only provide the concrete metatype for the value. Inside the context of staticType, even if T == Error, the body of staticType can't see that, so it picks the latter meaning of type(of:).

2 Likes

Can someone explain what does @Joe_Groff mean? I can see not different:

func staticType<T>(of value: T) -> String {
    return "\(type(of: value))"
}

vs.

func staticType<T>(of _: T) -> String {
    return "\(T.self)"
}

my test:

protocol P { }
struct X: P { }

let b = X() as P
let c: P = X()

both show P

So when do these two version show different result?

class Super {}
class Child: Super {}

func staticType1<T>(of value: T) -> String {
  "\(type(of: value))"
}

func staticType2<T>(of _: T) -> String {
  "\(T.self)"
}

// x's static type is Super
// x's dynamic type is Child
let x: Super = Child()

print(staticType1(of: x)) // Child
print(staticType2(of: x)) // Super
1 Like

So why are we calling both version staticType? I'm not clear what are we talking about here. My understanding is:

let x: Super = Child()

Child is the static type
Super is the dynamic

and that's just how class inheritance work. It's got nothing to do with SwiftProtocol.

Is Swift Class the same as ObjectiveC Class?

I used both versions to show the difference in implementations.

This is backwards. You're declaring x to have static type Super and underneath at runtime it's really a Child.

I'm using classes here because Joe mentioned that using type(of:) will return the dynamic type for class instances, which in this case is Child, not the expected Super. He also mentioned that even though this is incorrect for classes, it appears to work for protocol types because staticType has no idea that the passed value is a protocol type, so it doesn't look for the dynamic type.

2 Likes

:+1::pray: