简体   繁体   中英

Swift passing protocol variable to generic function

Can someone explain why is passing protocol var to a generic function an error in Swift?

protocol P {}
func f<T: P>(_: T) {}
func g(x: P) { f(x) } // Error

This, however, is not an error:

protocol P {}
func f(_: P) {}
func g(x: P) { f(x) }

I was just wondering what is the difference of the code generated by the compiler which makes it to reject the first example but in second case the generated code is good to go. Both seem to give the behavior I would expect.

Can someone explain why is passing protocol var to a generic function an error in Swift?

 protocol P {} func f<T: P>(_: T) {} func g(x: P) { f(x) } // Error

It's because currently non- @objc protocols don't conform to themselves . Therefore P cannot satisfy the generic placeholder T : P , as P is not a type that conforms to itself.

However in this particular example, that is, one where P doesn't have any static requirements, there's no fundamental limitation preventing P from conforming to itself (I explain this in more detail in the above linked Q&A). It's merely an implementation limitation.

What is the difference of the code generated by the compiler which makes it to reject the first example but in second case the generated code is good to go

Protocol-typed values (existentials) are implemented in a slightly different manner to generic-typed values constrained to a protocol.

A protocol-typed value P consists of:

  • An inline value buffer for the stored conforming value (currently 3 words in length, but is subject to change until ABI stability). If the value to store is more than 3 words in length, it's put into a heap allocated box, and a reference to this box is stored in the buffer.
  • A pointer to the conforming type's metadata.
  • A pointer to the protocol witness table for the conformance of the value to P , which lists the implementations to call for each of the protocol requirements.

On the flip side, a generic-typed value T where T : P consists of only the value buffer. The type metadata and witness table(s) are instead passed as implicit arguments to the generic function, and any member accesses or memory manipulations for values of type T can be done by consulting these arguments. Why? Because Swift's generics system ensures that two values of type T must be of the same type, so they must share the same conformance to the protocol constraint(s).

However this guarantee breaks down if we allow protocols to conform to themselves. Now, if T is a protocol type P , two values of type T could potentially have different underlying concrete types and therefore different conformances to P (so different protocol witness tables). We'd need to consult protocol witness tables on a per-value (rather than per-type) basis – just like we do with existentials.

So what we'd want is for generic-typed values to have the same layout as an existential of the protocol constraints. However this would make things pretty inefficient for the vast majority of the cases when the generic placeholder is not being satisfied by a protocol type, as values of type T would be carrying about redundant information.

The reason why @objc protocols are allowed to conform to themselves when they don't have static requirements is because they have a much simpler layout than non- @objc existentials – they just consist of a reference to the class instance, where protocol requirements are dispatched to via objc_msgSend . This layout is shared with that of a value typed as a placeholder T constrained to the protocol, which is why it's supported.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM