I would like to understand this section.
I'm reading Finding the Dynamic Type in a Generic Context that has this snippet:
func printGenericInfo<T>(_ value: T) {
let t = type(of: value)
print("'\(value)' of type '\(t)'")
}
protocol P {}
extension String: P {}
let stringAsP: P = "Hello!"
printGenericInfo(stringAsP)
// 'Hello!' of type 'P'
... that's followed up by this sentence:
This unexpected result occurs because the call to
type(of: value)insideprintGenericInfo(_:)must return a metatype that is an instance ofT.Type, butString.self(the expected dynamic type) is not an instance ofP.Type(the concrete metatype of value).
- Why is
String.selfnot an instance ofP.Typewhen I can run this code?
func f(_ t: P.Type) { print("...") }
f(String.self)
- Why does
type(of:)return the concrete metatype outside but not inside generic functions?
print("'\(stringAsP)' of type '\(type(of: stringAsP))'")
// 'Hello!' of type 'String'
For a protocol
P, there are two kinds of metatypes that you can write withP. You can write(any P).Type(akaP.Protocol), which is the metatype ofPitself, andany P.Type(akaP.Type), which is an existential type representing "the metatype of some type that conforms toP".String.selfis an instance ofany P.Type, but not(any P).Type. The documentation is incorrect here. It probably meant to say(any P).Type. Otherwise this whole thing doesn't make sense.type(of:)has two different behaviours depending on what kind of type its type parameterTis. YourprintGenericInfoalso has a type parameterT, so to avoid confusion, I will call themtypeof.TandprintGenericInfo.Trespectively.If
typeof.Tis a non-existential type, then it returns the metatype oftypeof.T. Iftypeof.Tis an existential (i.e. protocol) typeany E, it would return anany E.Type, not(any E).Type. After all, the former is much more useful - it can tell you the actual type that the existential type "wraps".type(of:)in this case needs to "unwrap" the existential and "look inside" of it.This difference in behaviour is exactly what your
printGenericInfolacks. In fact, this kind of semantics cannot be written in Swift's syntax. This is whytype(of:)has the weird signature it has - it returns aMetatypetype parameter, seemingly unrelated to the type it takes in.type(of:)uses special annotations to allow the compiler to type-checktype(of:)calls in a special way. Depending ontypeof.T,Metatypecould either betypeof.T.Type(whentypeof.Tis not existential) orany typeof.T.Type(whentypeof.Tis existential). Note that this is a compile time check. The type parameters of generic functions are decided at compile time, not at runtime.printGenericInfodoesn't unwrapprintGenericInfo.Tand look inside of it. It just directly passes that totype(of:), sotypeof.Tis decided to beprintGenericInfo.T, a type parameter, which is not an existential type.When you call
printGenericInfo(stringAsP),printGenericInfo.Tis decided to beany P- an existential type. This doesn't changetypeof.T, which is stillprintGenericInfo.T, a non-existential type. So at runtime,type(of:)returns the metatype ofany P, and that is(any P).Type.If you do
let t = type(of: value as Any)instead, thentype(of:)will see thattypeof.Tis an existential type (Any), and so it will unwrap the existential type and look inside of it.