For every type T
in Swift, there is an associated metatype T.Type
.
Let's try to write a generic function like staticSizeof
. We will only consider its declaration; implementation is trivial and unimportant here.
Out first try would be:
func staticSizeof<T>() -> Int
staticSizeof<Float>() // error :(
Unfortunately, it's an error. We can't explicitly specialize generic functions in Swift. Second try: we pass a parameter to our function and get generic type parameter from it:
func staticSizeof<T>(_: T) -> Int
staticSizeof(1) //=> should be 8
But what if our type T
was a bit more complex and hard to obtain? For example, think of struct Properties
that loads a file on initialization:
let complexValue = Properties("the_file.txt") // we have to load a file
staticSizeof(complexValue) // just to specialize a function
Isn't that weird? But we can work around that limitation by passing instance of a dummy generic type:
struct Dummy<T> { }
func staticSizeof<T>(_: Dummy<T>) -> Int
staticSizeof(Dummy<Float>())
This is the main detail to understand: we can explicitly specialize generic types, and we can infer generic type parameter of function from generic type parameter of passed instance. Now, surprise! We've already got Dummy<T>
in the language: it's called T.Type
and created using T.self
:
func staticSizeof<T>(_: T.Type) -> Int
staticSizeof(Float.self)
But there's a lot more to T.Type
. Sit tight.
Internally, T.Type
stores identifier of a type, which is an Int value. Specifically, T.Type
can refer to any subtype of T
. With enough luck, we can also cast instances of metatypes to other metatypes. For example, Int: CustomStringConvertible
, so we can do this:
let subtype = Int.self
metaInt //=> Int
let supertype = subtype as CustomStringConvertible.Type
supertype //=> Int
Here, supertype: CustomStringConvertible.Type
can refer to Int
, to String
or to any other T: CustomStringConvertible
.
We can also use as?
, as!
and is
to check subtyping relationships. I'll only show examples with is
:
Int.self is CustomStringConvertible.Type //=> true
protocol Base { }
protocol Derived: Base { }
Derived.self is Base.Type //=> true
protocol Base { }
struct Derived: Base { }
let someBase = Derived.self as Base.Type
// ...
someBase is Derived.Type //=> true
A common practise is to store metatypes as Any.Type
. When needed, we can check all required conformances.
If we have an instance of T.Type
, we can call static methods of T
on it:
struct MyStruct {
static func staticMethod() -> String { return "Hello metatypes!" }
}
let meta = MyStruct.self
meta.staticMethod() //=> Hello metatypes!
What is especially useful, if our T.self
actually stores some U:T
, then static method of U
will be called:
protocol HasStatic { static func staticMethod() -> String }
struct A: HasStatic { static func staticMethod() -> String { return "A" } }
struct B: HasStatic { static func staticMethod() -> String { return "B" } }
var meta: HasStatic.Type
meta = A.self
meta.staticMethod() //=> A
meta = B.self
meta.staticMethod() //=> B
Summing that up, metatypes have far deeper semantics than a tool for specialization of generic functions. They combine dynamic information about a type with static information "contained type is a subtype of this". They can also dynamically dispatch static methods the same way as normal methods are dynamically dispatched.
For protocols P
, besides normal P.Type
, there is also a "restricting metatype" P.Protocol
that is the same as P.Type
, except that it can only reflect P
itself and not any of its subtypes:
Int.self is CustomStringConvertible.Type //=> true
Int.self is CustomStringConvertible.Protocol //=> false
I personally don't understand, why P.Protocol
is needed. Even without P.Protocol
, we can test for equality:
Int.self is CustomStringConvertible.Type //=> true
Int.self == CustomStringConvertible.self //=> false
For protocols P
, P.self
returns a P.Protocol
, not P.Type
:
let meta = CustomStringConvertible.self
print(dynamicType(meta)) //=> CustomStringConvertible.Protocol
In practise, the existence of P.Protocol
creates problems. If T
is a generic parameter, then T.Type
turns into P.Protocol
if a protocol P
is passed:
func printMetatype<T>(_ meta: T.Type) {
print(dynamicType(meta))
let copy = T.self
print(dynamicType(copy))
}
printMetatype(CustomStringConvertible.self) //=> CustomStringConvertible.Protocol x2
Finally, we can understand the confusing situation!
func doesIntConformTo<T>(_: T.Type) -> Bool {
return Int.self is T.Type
}
doesIntConformTo(CustomStringConvertible.self) //=> false (?!)
Now we understand that because T
is a protocol, T.Type
turns into a .Protocol
, and we get the confusing behaviour.
The suggestion is to remove P.Protocol
from the language.