Metatypes in Swift

I am a Swift language evangelist in Africa that is I teach Swift in coding classes and bootcamps. In one of the coding classes, someone asked the purpose and use of Swift protocol metatype and I couldn't answer her. Please can someone help me with explanatory notes and code asap?

You could refer to my stack overflow post regarding the difference between .Type and .self where I also explain some dangerous areas of the hidden .Protocol metatype.

In short: Protocols do not conform to themselves. Usually metatypes follow the same subtyping relationship as the types they describe (if T : R -> T.Type : R.Type and also T.Type : T.Type), but for protocols currently we need an extra metatype that do not follow this rules (P -> P.Type : P.Type but it's currently impossible to get an instance of P.Type see the stack overflow post, instead you'll get P.Protocol which does not conform to P.Type but only to Any.Type).

Here is also a quite huge and theoretical proposal that currently is deferred and out of scope for the current and maybe a few next major language iterations.

Have a great day exploring ;)

3 Likes

All types in Swift have metatypes, including metatypes themselves, recursively. Metatype values describe types, e.g. for the purposes of reflection. Currently Swift's standard reflection facilities aren't very strong, but you can at least ask whether a metatype is equal to — or a subclass of — some other metatype.

Normally you name the metatype with X.Type, but we decided a long time ago that it was more "fluent" for Printable.Type to be the "existential" metatype, i.e. a type that can store any type that conforms to Printable. This feature can be very useful if, for example, Printable contains static methods or initializers that allow you to construct a value of the Self type. But because we made the syntax X.Type mean something different when X was a protocol type, we had to come up with a way of spelling the normal metatype of the protocol type — which is not a very useful feature, but which still has to exist because all types have metatypes. That's what Printable.Protocol is: a way of spelling something that's not generally useful but which still has to have a spelling.

5 Likes

Thanks, but she emphasized only .Protocol not .Type

This is a good opportunity to also ask a question. Why can you pass something of type P.Protocol to a function that expects an argument of type T.Type, given that T is inferred to be P? Is this simply a convenience so you could pass protocol types as well?

protocol P {}

func foo1<T>(_ arg: T) {
  print(T.self)
}
func foo2<T>(_ arg: T.Type) {
  print(T.self)
}

foo1(P.self) // prints P.Protocol
foo2(P.self) // prints P

It helps to break this away from the syntax.

Let metatype(T) be the metatype of T, i.e. the type of the type-value T.self. Then:

  • String.Type means metatype(String)
  • P.Protocol means metatype(P)
  • P.Type means ∃ τ : P . metatype(τ)

A generic function expecting T.Type is expecting metatype(T). So the special case here is not that you can pass P.self (a value of type P.Protocol) as a parameter of type T.Type. The special case is that metatype(P) is spelled differently from every other ordinary metatype.

This is confusing, I know. Using P.Type as the spelling for the "existential" metatype was probably a mistake.

11 Likes