Generics with protocol restriction always use protocol implementation of functions not type implementation

Hi! I have kind of design question about generics which use protocol with default implementation of functions. Assume I have such a common design pattern used a lot in Swift.

protocol SomeProtocol {}

extension SomeProtocol {
  func function() {
    print("protocol")
  }
}

class SomeClass: SomeProtocol {
  func function() {
    print("class")
  }
}

In this case I can choose which implementation of function() will be used by declaring type of variables.

let protocolVar: SomeProtocol = SomeClass()
let classVar = SomeClass()

protocolVar.function() // prints "protocol"
classVar.function() // prints "class"

But when I use generic function I always receive protocol implementation. For example, I have two functions:

func basicFunction(_ parameter: SomeProtocol) {
  parameter.function()
}

func genericFunction<T: SomeProtocol>(_ parameter: T) {
  parameter.function()
}

In first case (with basicFunction()) I tell the compiler that I want to use protocol implementation of function(), so behaviour for classVar and protocolVar are expected:

basicFunction(protocolVar) // prints "protocol"
basicFunction(classVar) // prints "protocol"

In second case (with genericFunction() I tell the compiler that this function will only work with specific type which implements certain protocol. That's why I receive compile error when trying to pass protocolVar to genericFunction()

genericFunction(protocolVar) // compile error: Cannot invoke 'genericFunction' with an argument list of type '(parameter: SomeProtocol)'

But when I use genericFunction() with classVar I receive protocol behaviour

genericFunction(classVar) // prints "protocol"

It's a little bit confusing since compiler doesn't allow me to pass protocolVar to genericFunction() and that's OK. But if I pass variable with appropriate type (class) I receive not this type behaviour but protocol one.

Of course, I can receive class behaviour of function() if I'll declare func function() in protocol body, so every class which implements this protocol require to have it's own implementation. But in such a case I'm not able to receive default protocol implementation of function().

So my suggestion is to use type implementation in generic functions and if this function is not implemented in type declaration use default implementation from protocol extension. It will make generics more flexible as for me.

What do you think? Maybe I'm missing something?

And sorry if such a suggestion was already discussed here, I've tried to find ;)

I've added all the code from this proposal to Playground here.

Isn't this simply due to static dispatch of your protocol extension which is not a requirement?! Please correct me anyone if I'm wrong.

protocol Foo {}

extension Foo {
  func foo() {
    print("foo protocol")
  }
}

protocol Bar {
  func bar()
}

extension Bar {
  func bar() {
    print("bar protocol")
  }
}

class SomeClass: Foo, Bar {
  func foo() {
    print("foo class")
  }

  func bar() {
    print("bar class")
  }
}

func basicFooFunction(_ parameter: Foo) {
  parameter.foo()
}

func genericFooFunction<T: Foo>(_ parameter: T) {
  parameter.foo()
}

func basicBarFunction(_ parameter: Bar) {
  parameter.bar()
}

func genericBarFunction<T: Bar>(_ parameter: T) {
  parameter.bar()
}

let classInstance = SomeClass()
let protocolFoo: Foo = classInstance
let protocolBar: Bar = classInstance

classInstance.foo() // foo class
classInstance.bar() // bar class
protocolFoo.foo()   // foo protocol
protocolBar.bar()   // bar class

basicFooFunction(classInstance) // foo protocol
basicFooFunction(protocolFoo)   // foo protocol

basicBarFunction(classInstance) // bar class
basicBarFunction(protocolBar)   // bar class

genericFooFunction(classInstance) // foo protocol
genericBarFunction(classInstance) // bar class

// expected to emit an error since protocols do not conform to themselfs
// genericFooFunction(protocolFoo)
// genericBarFunction(protocolBar)

I think the answer of @DevAndArtist is correct and clear.
When declare a function in protocol, swift will use a dynamic dispatch to find which function to call.
When declare a function in extension of protocol only, swift will do static dispatch to that function.
There are two WWDC session would make it clear and solid. First one would introduce the concept part and Second one would show some implementation details.
Swift Generics
Understanding Swift Performance
Please correct me if I'm wrong.:relaxed:

For some reason though I seem to remember that the compiler will generate specified functions for generic functions for some technical reasons that I may not understand myself or simply forgot already. In that situation it would become genericFooFunction(_: SomeClass), so I'm not sure why it's still calling the default implementation instead of the concrete implementation provided by SomeClass. I mean in that situation T has become SomeClass and shouldn't any longer be an existential of type Foo.

I think @Slava_Pestov can answer that question more precisely.


Edit: I think it was mentioned somewhere in this talk, but it also could be only an optimization thing.

Is the SomeClass a Existential Container?

Sorry I'm not quite sure what you're asking here.

From [Understanding Swift Performance], I learned that in protocol type polymorphism, as depicted by func genericFunction<T: SomeProtocol>(_ parameter: T),
swift would use a existential container to wrap the value of parameter, and inside container there is two pointers referring to Value Witness Table (which describes the wrapped value) and Protocol Witness Table (which show functions respecting to protocol, local implementation or protocol default implementation ).

Therefore, I asked you if SomeClass is a Existential Container.:relaxed:
Sorry for making you confused.

@petertretyakov regardless the technical reasons that you hit with your pattern you can workaround the issue like this:

protocol A {}

extension A {
  func a() {
    print("a protocol")
  }
}

protocol B : A {
  func a()
}

class SomeClass: B {
  func a() {
    print("a class")
  }
}

func genericFunction<T : B>(_ parameter: T) {
  parameter.a()
  (parameter as A).a()
}

let classInstance = SomeClass()
genericFunction(classInstance)
// prints:
// a class
// a protocol

I would need to re-watch the session and the talk I linked above to actually answer it more or less correctly. The whole topic about existentials isn't that hard, but it's a little bit confusing in some situations. :slight_smile:

Thank you @DevAndArtist and @KeithTsui for your responses and links! I'm not sure though that I can keep your conversation about Existential Containers and Dispatch types due to lack of some knowledge about it but I'll check links to videos you provided ;)

Adrian you're right that I can get class implementation of function with second protocol. But would it be more convenient to have such a behaviour without this second protocol? Since generics place restrictions on type which we can use in function I don't understand why these restrictions works on function declaration level but not on function body. So you can be sure that generic function receives certain type conforming to protocol not protocol type itself but you can't be sure that it will provide you behaviour of that type. Instead it provides you behaviour of protocol.

It would be great if someone can describe negative results of my proposal. Because I see only pros of it but I'm sure that there're some cons also.

The obvious con. is that it will be a breaking change if all generic algorithms out there would start operate on the concrete implementation of the conforming type where the implementation is not a requirement from the protocol itself. It will simply mess up everything and create unexpected behaviors all over the place. That's why I don't think we can just do it. However I think we might be able to access the default implementation one day (assuming the compiler knows there is a default implementation - not sure if the source could should be around or not though). This is part of the Maybe section in the generic manifesto here. I learned the behavior of static vs. dynamic dispatch and I think it will stay forever now, but we can add a way to access the default implementation regardless of the protocol requirement.

Bikeshedding syntax:

protocol SomeProto {
   func function()
}

extension SomeProto {
  func function() {
    print("SomeProto.function")
  }
}
class SomeClass : SomeProto {
   func function() {
     print("SomeClass.function")
     default.function() // prints SomeProto.function
   }
}
1 Like

Yes, specialising generic functions for concrete is an optimisation, and doesn't (or, at least, shouldn't) change the behaviour of the code, just makes it go faster.


Your original comment is correct: this is due to static dispatch, because function wasn't chosen to be a customisation point of the protocol.

2 Likes