Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Override of protocol default implementation in a subsubclass doesn't participate in dynamic dispatch

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?

like image 686
Oleksii Taran Avatar asked Aug 17 '16 23:08

Oleksii Taran


1 Answers

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"} }
like image 100
Luca Angeletti Avatar answered Oct 30 '22 15:10

Luca Angeletti