Differences generic protocol type parameter vs direct protocol type
The key confusion is that Swift has two concepts that are spelled the same, and so are often ambiguous. One of the is struct T: A {}
, which means "T conforms to the protocol A," and the other is var a: A
, which means "the type of variable a
is the existential of A."
Conforming to a protocol does not change a type. T
is still T
. It just happens to conform to some rules.
An "existential" is a compiler-generated box the wraps up a protocol. It's necessary because types that conform to a protocol could be different sizes and different memory layouts. The existential is a box that gives anything that conforms to protocol a consistent layout in memory. Existentials and protocols are related, but not the same thing.
Because an existential is a run-time box that might hold any type, there is some indirection involved, and that can introduce a performance impact and prevents certain optimizations.
Another common confusion is understanding what a type parameter means. In a function definition:
func f<T>(param: T) { ... }
This defines a family of functions f<T>()
which are created at compile time based on what you pass as the type parameter. For example, when you call this function this way:
f(param: 1)
a new function is created at compile time called f<Int>()
. That is a completely different function than f<String>()
, or f<[Double]>()
. Each one is its own function, and in principle is a complete copy of all the code in f()
. (In practice, the optimizer is pretty smart and may eliminate some of that copying. And there are some other subtleties related to things that cross module boundaries. But this is a pretty decent way to think about what is going on.)
Since specialized versions of generic functions are created for each type that is passed, they can in theory be more optimized, since each version of the function will handle exactly one type. The trade-off is that they can add code-bloat. Do not assume "generics are faster than protocols." There are reasons that generics may be faster than protocols, but you have to actually look at the code generation and profile to know in any particular case.
So, walking through your examples:
func direct(a: A) {
// Doesn't work
let _ = A.init(someInt: 1)
}
A protocol (A
) is just a set of rules that types must conform to. You can't construct "some unknown thing that conforms to those rules." How many bytes of memory would be allocated? What implementations would it provide to the rules?
func indirect<T: A>(a: T) {
// Works
let _ = T.init(someInt: 1)
}
In order to call this function, you must pass a type parameter, T, and that type must conform to A. When you call it with a specific type, the compiler will create a new copy of indirect
that is specifically designed to work with the T you pass. Since we know that T has a proper init, we know the compiler will be able to write this code when it comes time to do so. But indirect
is just a pattern for writing functions. It's not a function itself; not until you give it a T to work with.
let a: A = B(someInt: 0)
// Works
direct(a: a)
a
is an existential wrapper around B. direct()
expects an existential wrapper, so you can pass it.
// Doesn't work
indirect(a: a)
a
is an existential wrapper around B. Existential wrappers do not conform to protocols. They require things that conform to protocols in order to create them (that's why they're called "existentials;" the fact that you created one proves that such a value actually exists). But they don't, themselves, conform to protocols. If they did, then you could do things like what you've tried to do in direct()
and say "make a new instance of an existential wrapper without knowing exactly what's inside it." And there's no way to do that. Existential wrappers don't have their own method implementations.
There are cases where an existential could conform to its own protocol. As long as there are no init
or static
requirements, there actually isn't a problem in principle. But Swift can't currently handle that. Because it can't work for init/static, Swift currently forbids it in all cases.
Difference between using Generic and Protocol as type parameters, what are the pros and cons of implement them in a function
There is actually a video from this year's WWDC about that (it was about performance of classes, structs and protocols; I don't have a link but you should be able to find it).
In your second function, where you pass a any value that conforms to that protocol, you are actually passing a container that has 24 bytes of storage for the passed value, and 16 bytes for type related information (to determine which methods to call, ergo dynamic dispatch). If the passed value is now bigger than 24 bytes in memory, the object will be allocated on the heap and the container stores a reference to that object! That is actually extremely time consuming and should certainly be avoided if possible.
In your first function, where you use a generic constraint, there is actually created another function by the compiler that explicitly performs the function's operations upon that type. (If you use this function with lots of different types, your code size may, however, increase significantly; see C++ code bloat for further reference.) However, the compiler can now statically dispatch the methods, inline the function if possible and does certainly not have to allocate any heap space. Stated in the video mentioned above, code size does not have to increase significantly as code can still be shared... so the function with generic constraint is certainly the way to go!
Swift P.Protocol vs P.Type
P.Protocol
is the metatype for the protocol P
, just like T.Type
is the metatype for the non-protocol type T
. There is no "conversions" going on.
So what is P.Type
then?
P.Type
is an existential metatype. There is no corresponding thing for non-protocol types. Say you want to store metatypes of concrete types that conform to P
, you can store it in P.Type
:
let x: P.Type = C.self
Note that P.Type
can only store metatypes of concrete types, and P.Protocol
can only store metatypes of protocols. To see why this division is significant, let's say P
defines the static method foo
, you can then call x.foo()
. This is guaranteed to work because P.Type
must have a concrete type, which will implement foo
. On the other hand, you can't call foo
on P.Protocol
, because there is no implementation of foo
in P
.
See also: https://swiftrocks.com/whats-type-and-self-swift-metatypes
Understanding swift generics vs treating parameters as a protocol or base type
In the case of protocols, it depends on the protocol itself. If the protocol uses Self
or a typealias
, it cannot be used directly. For any other protocol, you can declare variables and parameters of type protocol<MyProtocol>
, e.g., var o: protocol<MyProtocol>
.
The reason you can't say var o: protocol<Equatable>
is because the Equatable
protocol is designed in a way that certain constraints it declares (in this case Self
) must be satisfied, and thus it can only be used as a generic type constraint. In other words, the compiler must be able to figure out at compile time what Self
is in regards to anything that is Equatable
, and it cannot (always) do that in var o: protocol<Equatable>
.
Why use generics rather than protocols or base classes? Because generics can be even more general than those while still being type-safe. This is especially useful, for instance, with something like a callback. Here's a very contrived example:
class Useless<T> {
private let o: T
private let callback: (T, String) -> Void
required init(o: T, callback: (T, String) -> Void) {
self.o = o
self.callback = callback
}
func publish(message: String) {
callback(o, message)
}
}
var useless = Useless(o: myObject) { obj, message in
// Here in the callback I get type safety.
obj.someMethod(message)
}
(This code has never been run by anyone ever. It should be regarded as pseudo-code.)
Now, this is a pretty silly example for many reasons, but it illustrates the point. Thanks to generics, the obj
parameter of the callback is entirely type-safe. This could not be done with base classes or protocols because we could never anticipate what code might get called in the callback. The Useless
class can take any type as its T
.
What's the difference between using or not using the 'where' clause with generics?
This is clearly stated in the Swift guide:
The requirements in a generic where clause specify that a type
parameter inherits from a class or conforms to a protocol or protocol
composition. Although the generic where clause provides syntactic
sugar for expressing simple constraints on type parameters (for
instance,<T: Comparable>
is equivalent to<T> where T: Comparable
and
so on), you can use it to provide more complex constraints on type
parameters and their associated types. For instance, you can constrain
the associated types of type parameters to conform to protocols. For
example,<S: Sequence> where S.Iterator.Element: Equatable
specifies
thatS
conforms to theSequence
protocol and that the associated type
S.Iterator.Element
conforms to theEquatable
protocol. This constraint
ensures that each element of the sequence is equatable.
Simply put, where
lets you specify constraints about the associated types of a generic parameter, while in <>
you can't do this.
Why can't I pass a Protocol.Type to a generic T.Type parameter?
P.Type
vs. P.Protocol
There are two kinds of protocol metatypes. For some protocol P
, and a conforming type C
:
- A
P.Protocol
describes the type of a protocol itself (the only value it can hold isP.self
). - A
P.Type
describes a concrete type that conforms to the protocol. It can hold a value ofC.self
, but notP.self
because protocols don't conform to themselves (although one exception to this rule isAny
, asAny
is the top type, so any metatype value can be typed asAny.Type
; includingAny.self
).
The problem you're facing is that for a given generic placeholder T
, when T
is some protocol P
, T.Type
is not P.Type
– it is P.Protocol
.
So if we jump back to your example:
protocol P {}
class C : P {}
func printType<T>(serviceType: T.Type) {
print(serviceType)
}
let test: P.Type = C.self
// Cannot invoke 'printType' with an argument list of type '(serviceType: P.Type)'
printType(serviceType: test)
We cannot pass test
as an argument to printType(serviceType:)
. Why? Because test
is a P.Type
; and there's no substitution for T
that makes the serviceType:
parameter take a P.Type
.
If we substitute in P
for T
, the parameter takes a P.Protocol
:
printType(serviceType: P.self) // fine, P.self is of type P.Protocol, not P.Type
If we substitute in a concrete type for T
, such as C
, the parameter takes a C.Type
:
printType(serviceType: C.self) // C.self is of type C.Type
Hacking around with protocol extensions
Okay, so we've learnt that if we can substitute in a concrete type for T
, we can pass a C.Type
to the function. Can we substitute in the dynamic type that the P.Type
wraps? Unfortunately, this requires a language feature called opening existentials, which currently isn't directly available to users.
However, Swift does implicitly open existentials when accessing members on a protocol-typed instance or metatype (i.e it digs out the runtime type and makes it accessible in the form of a generic placeholder). We can take advantage of this fact in a protocol extension:
protocol P {}
class C : P {}
func printType<T>(serviceType: T.Type) {
print("T.self = \(T.self)")
print("serviceType = \(serviceType)")
}
extension P {
static func callPrintType/*<Self : P>*/(/*_ self: Self.Type*/) {
printType(serviceType: self)
}
}
let test: P.Type = C.self
test.callPrintType()
// T.self = C
// serviceType = C
There's quite a bit of stuff going on here, so let's unpack it a little bit:
The extension member
callPrintType()
onP
has an implicit generic placeholderSelf
that's constrained toP
. The implicitself
parameter is typed using this placeholder.When calling
callPrintType()
on aP.Type
, Swift implicitly digs out the dynamic type that theP.Type
wraps (this is the opening of the existential), and uses it to satisfy theSelf
placeholder. It then passes this dynamic metatype to the implicitself
parameter.So,
Self
will be satisfied byC
, which can then be forwarded ontoprintType
's generic placeholderT
.
Why is T.Type
not P.Type
when T == P
?
You'll notice how the above workaround works because we avoided substituting in P
for the generic placeholder T
. But why when substituting in a protocol type P
for T
, is T.Type
not P.Type
?
Well, consider:
func foo<T>(_: T.Type) {
let t: T.Type = T.self
print(t)
}
What if we substituted in P
for T
? If T.Type
is P.Type
, then what we've got is:
func foo(_: P.Type) {
// Cannot convert value of type 'P.Protocol' to specified type 'P.Type'
let p: P.Type = P.self
print(p)
}
which is illegal; we cannot assign P.self
to P.Type
, as it's of type P.Protocol
, not P.Type
.
So, the upshot is that if you want a function parameter that takes a metatype describing any concrete type that conforms to P
(rather than just one specific concrete conforming type) – you just want a P.Type
parameter, not generics. Generics don't model heterogenous types, that's what protocol types are for.
And that's exactly what you have with printType(conformingClassType:)
:
func printType(conformingClassType: P.Type) {
print(conformingClassType)
}
printType(conformingClassType: test) // okay
You can pass test
to it because it has a parameter of type P.Type
. But you'll note this now means we cannot pass P.self
to it, as it is not of type P.Type
:
// Cannot convert value of type 'P.Protocol' to expected argument type 'P.Type'
printType(conformingClassType: P.self)
Is there a practical difference between a type constraint on a generic type directly vs using a 'where' clause?
There is no difference. The first form
func testX<T>(value: T) where T: StringProtocol
was introduced with SE-0081 Move where clause to end of declaration to increase readability, in particular for longer lists of constraints. The rationale was to remove the where
clause out of the generic parameter list, for example
func foo<S: Sequence where S.Element == Int>(seq: S)
became
func foo<S: Sequence>(seq: S) where S.Element == Int
in Swift 3. As a side-effect, even simple constraints such as
your T: StringProtocol
can be moved to the newly introduced where-clause.
Difference between extension and direct call for protocol
I agree with matt that the underlying answer is Protocol doesn't conform to itself?, but it's probably worth answering anyway, since in this case the answer is very simple. First, read the linked question about why [Protocol]
doesn't work the way you think it does (especially Hamish's answer, which is much more extensive than the accepted answer that I wrote). [Protocol]
does not match the where Element: Protocol
clause because Protocol
is not a concrete type that conforms to Protocol
(because it's not a concrete type).
But you don't need [Protocol]
here. You have T: Protocol
, so you can (and should) just use that:
var arr = [T]()
With that change, the rest should work as you expect because T
is a concrete type that conforms to Protocol.
Related Topics
Using Codable to Encode/Decode from Strings to Ints with a Function in Between
How to Make Xcode Put Starting Brace on New Line in Swift
Dismiss View from View Model [Modal Page]
Set a Variable to the < ("Less Than") Operator as a Function in Swift
Usage of Withmemoryrebound with Apples Swift 3 Beta 6
Name Convention for Unwrapped Value in Swift
How to Add Documentation to Enum Associated Values in Swift
No Designated Init for Skshapenode(Circleofradius: Radius)
Table View Cellforrowatindexpath Warning
"Message from Debugger: Unable to Attach" When Running Tests on Osx App
What's the Rationale of Swift's Size Methods Taking 'Int'S
Swift: Oslog/Os_Log Not Showing Up in Console App
Take Screenshot of Host App Using iOS Share/Action Extensions