Protocol extension, generics and dynamic dispatch confusion

Hello,

I've been scratching my head for the past few days over protocols / dynamic dispatch behaviour I'm observing. It's not easy to explain the entire setup so I've distilled my code to the bare minimum that still reproduces the issue.

Here's the basic setup:

protocol Foo {
  associatedtype T
  var foo: T { get }
}

protocol Bar {
  var bar: String { get }
}

extension Bar where Self: Foo {
  var bar: String { "I'm foo" }
}

extension Bar where Self: Foo, Self.T: Numeric {
  var bar: String { "Deep down I'm a number" }
}

Since bar is declared in protocol requirements I expect it to be dynamically dispatched to bar properties.

Here's a snippet that tests this assumption:

struct PlainFoo: Foo, Bar {
  var foo = "Hello"
}

struct IntFoo: Foo, Bar {
  var foo: Int = 3
}

let plainFoo = PlainFoo()
let intFoo = IntFoo()

print(plainFoo.bar) // "I'm foo"
print(intFoo.bar)   // "Deep down I'm a number"

let bars: [Bar] = [plainFoo, intFoo]
bars.forEach { print($0.bar) }
// prints:
// "I'm foo"
// "Deep down I'm a number"

And it is in fact correctly dispatched! Value of bar is correct both when I reference my object by their type as well as when I call bar from a collection of [Bar] type.

Now things do not work the same way with generics:

struct GenericFoo<V>: Foo, Bar {
  var foo: V
}

let genericFooString = GenericFoo(foo: "Hello")
let genericFooInt = GenericFoo<Int>(foo: 3)

// Correct dynamic dispatch to bar:
print(genericFooString.bar) // "I'm foo"
print(genericFooInt.bar)    // "Deep down I'm a number"

// Things go wrong here:
let genericBars: [Bar] = [genericFooString, genericFooInt]
genericBars.forEach { print($0.bar) }
// prints:
// "I'm foo"
// "I'm foo" <- should print "Deep down I'm a number" instead!

Although bar is correctly called when calling it on an object, calling bar when I iterate over a collection of [Bar] doesn't dispatch the call as I would expect based on all the previous examples.

Can anyone help me understand what's going on? How can I make Swift call correct implementation of bar in that last example?

Thanks!
Michal

1 Like

This came up recently:

Just like in Java and C++, you need to use the "double dispatch" pattern to accomplish this sort of thing: Double dispatch - Wikipedia

2 Likes

Thanks for your reply @Slava_Pestov! I read up on double dispatch pattern and although I don't know how to apply it to my example yet I tried implementing it as per wikipedia examples and I've hit an issue I'd like to understand before I continue.

Here's my setup:

protocol Foo {
  func bar(using provider: Bar) -> String
}

extension Foo {
  func bar(using provider: Bar) -> String {
    provider.bar(for: self)
  }
}

extension Foo where Self: Buz {
  func bar(using provider: Bar) -> String {
    provider.bar(for: self)          // <--- breakpoint 1
  }
}

// ---

protocol Bar {
  func bar(for: some Foo) -> String // same behaviour when declared as func bar<T: Foo>(for: T) -> String
}

extension Bar {
  // Implementation for any Foo
  func bar(for: some Foo) -> String {
    "plain foo"
  }
  
  // Special treatment for Foo that also conforms to Buz
  func bar<T: Foo>(for: T) -> String where T:Buz {
    "buz foo"           // <--- breakpoint 2
  }
}

// ---

protocol Buz {} // Special treatment when computing bar

// ----

struct Plain: Foo {}
struct Complex: Foo, Buz {}

struct SomeBar: Bar {}

let plain = Plain()
let complex = Complex()

let someBar = SomeBar()

let elements: [Foo] = [plain, complex]

elements.forEach {
  print($0.bar(using: someBar)) // prints "plain foo" twice.
}

The first part of "double dispatch" pattern works correctly - I'm hitting the breakpoint 1 (see comment in code), however breakpoint 2 is never hit, both calls to func bar end up in the implementation that returns "plain foo".

Can you please help me understand if what I'm trying to do here is possible?

The only way to get "buz foo" returned I found was to add following method to Buz:

extension Bar {
  ...
  
  func bar(for: Buz) -> String {
    "buz foo"
  }
}

However defining input param as Buz doesn't work for me as I need to be able to reference properties of Foo in those methods as well.

I've also verified that this non-generic code works ok:

struct Foo {
  func sayHello<T>(to object: T) -> String {
    "Hello!"
  }
  
  func sayHello<T>(to object: T) -> String where T: Numeric {
    "Hello number!"
  }
}

let foo = Foo()
print(foo.sayHello(to: "")) // Hello!
print(foo.sayHello(to: 3))  // Hello number!

Discussing this with Foo's and Bar's is getting a bit confusing - I'm happy to provide more of a "real world" context for what I'm trying to achieve if you think would be helpful.

1 Like

I was able to get the desired behavior in your example by adding a second protocol requirement to protocol Bar:

func bar(for: some Foo & Buz) -> String

What this changes is that inside this extension, the call to provider.bar now refers to the new protocol requirement:

extension Foo where Self: Buz {
  func bar(using provider: Bar) -> String {
    provider.bar(for: self)          // <--- here
  }
}

This means that when you declare this conformance,

struct Complex: Foo, Buz {}

The implementation of Foo.bar() calls the new Bar.bar() requirement.

In this conformance,

struct SomeBar: Bar {}

The new requirement has this implementation (which already exists in your code):

  // Special treatment for Foo that also conforms to Buz
  func bar<T: Foo>(for: T) -> String where T:Buz {
    "buz foo"           // <--- breakpoint 2
  }

This means that when you call Complex.bar() in the for loop, it dispatches to the new implementation.

You can also write that method signature like this, by the way,

func bar(for: some Foo & Buz) -> String

I'm used to reading math papers so Foo and Bar are more than good enough :slight_smile:

1 Like