10

Consider following playground:

protocol A {
    func f() -> String
}

extension A {
    func f() -> String { return "AAAA" }
}

class B: A {}

class C: B {
    func f() -> String { return "CCCC" }
}

let a: A = C()
let b: B = C()
let c: C = C()

a.f() // "AAAA" - why?
b.f() // "AAAA" - why?
c.f() // "CCCC"

I don't get why a.f() and b.f() return "AAAA" - they are supposed to return "CCCC" because func f() -> String should be dynamically dispatched (as it declared in protocol).

If I change class B to look like this:

class B: A {
    func f() -> String { return "BBBB" }
}

then all three calls to .f() return "CCCC" as expected.

I feel like it is a bug in Swift compiler, I checked in Xcode 7.3.1 and 8.0-beta3, this behavior is reproducible in both.

Is this actually an expected behavior?

Oleksii Taran
  • 348
  • 3
  • 10
  • @matt, I don't agree that this question is a duplicate because base protocol declares `f()` method as a requirement, not as an extension, thus the linked question doesn't answer my question. Please consider reopening this question. – Oleksii Taran Aug 19 '16 at 20:05
  • It's a duplicate because the answer answers your question. – matt Aug 19 '16 at 20:19
  • Also don't think this is a duplicate. – Richard Birkett Dec 21 '16 at 15:20
  • 1
    Although similar, I also don't think this a duplicate. Having the same answer does not make a question a duplicate. Eg. "What is 2+1?" and "What is the first digit of π?". Not duplicate questions, same answers. – user2067021 Apr 24 '17 at 04:39
  • Quoting a friend, the tldr is: "Only the class that declares the conformance gets a protocol witness table". For that reason the subclass is oblivious to the protocol – mfaani Nov 09 '21 at 19:25

1 Answers1

4

Thee are several rules involved here.

Sometimes Static Dispatch is used (in this case we must look at the type of the var/let to find out the implementation that will be used).

Other times Dynamic Dispatch is used instead (this means the implementation of the object inside the variable is used).

Let's consider the general example

let foo: SomeType1 = SomeType2()
foo.f()

I'll use the following definitions

  • classic implementation of f() to indicate when f() is defined outside of a protocol extension (so inside a struct/class).

  • default implementation of f() to indicate when f() is defined inside a protocol extension.

Dynamic Dispatch

If SomeType1 is a struct/class with it's own "classic" implementation of f() then polymorphism is applied.

It means that if SomeType2 doesn't have a classic implementation of f() then SomeType1.f() is used. Otherwise SomeType2.f() wins.

Static Dispatch

If SomeType1 doesn't have a classic implementation of f() but has a default implementation, then polymorphism is turned off.

In this case default implementation of the type of the let/var wins.

a.

Let's look at your first example

let a: A = C()
a.f() // "AAAA"

In this A doesn't have it's own classic implementation (because it's not a struct/class) but has a default implementation. So polymorphism is turned off and A.f() is used.

b.

Same rule for your second example

let b: B = C()
b.f() // "AAAA"

B doesn't have classic implementation of f(), but has a default implementation of f(). So polymorphism is turned off and B.f() (from the protocol extension) is used.

c.

Finally the object of type C is inside a constant of type C.

var c:C
c.f() // "CCCC"

Here C has a classic implementation of f(). In this case the protocol implementation is ignored and C.f() is used.

More

Let's see another example

protocol Alpha { }
extension Alpha { func f() -> String { return "Alpha"} }
protocol Beta { }
extension Beta { func f() -> String { return "Beta"} }

class Foo: Alpha, Beta { }

let alpha: Alpha = Foo()
alpha.f() // "Alpha"

let beta: Beta = Foo()
beta.f() // "Beta"

As you can see, again, the type of the constant containing the value wins. And if you put the Foo object inside a Foo constant you get a compile error

let foo: Foo = Foo()
foo.f() //

error: ambiguous use of 'f()'
foo.f()
^
Swift 2.playground:2:23: note: found this candidate
extension Beta { func f() -> String { return "Beta"} }
                      ^
Swift 2.playground:6:24: note: found this candidate
extension Alpha { func f() -> String { return "Alpha"} }
Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
  • I have declared `f()` inside a protocol A (i.e. as a requirement), this should force dynamic dispatch (as described at https://nomothetis.svbtle.com/the-ghost-of-swift-bugs-future). – Oleksii Taran Aug 19 '16 at 20:11
  • 3
    Joe Groff (one of Swift compiler authors) commented on this issue: https://twitter.com/jckarter/status/766739014237892608 – Oleksii Taran Aug 19 '16 at 21:36