[swift-users] Protocol extension gotcha... or bug?

Wagner Truppel trupwl at gmail.com
Mon Jan 23 09:31:37 CST 2017


Say you have a protocol and an extension of it, like so:

protocol P {
   var item: String { get }
}

extension P {
   var item: String {
       return "P"
   }
}

Now imagine a class that conforms to that protocol and uses the property declared there but doesn’t specialise it in any way:

class A: P {
   var usedItem: String {
       return item // A uses 'item' but doesn't override or specialise it in any way
   }
}

let a = A()
a.item         // returns “P"
a.usedItem // returns “P"

Not surprisingly, both results equal “P” since A doesn’t specialise 'item'. Now imagine a class B that subclasses A and *does* specialise 'item':

class B: A {
   var item: String { // subclass specialises 'item' by "overriding" the protocol extension implementation
       return "B"
   }
}

let b = B()
b.item // returns “B”

No surprise there either. B specialises 'item' so that’s what gets used. But...

b.usedItem // returns “P” !!?

I can hear you thinking "That's because 'usedItem' is being statically dispatched, since it's not declared in the protocol" but adding a declaration for 'usedItem' to the protocol doesn't help! I'll get to that in a moment.

Now consider class C, which is similar to B but also overrides A’s implementation of 'usedItem' to do, well, exactly what A does (at least syntactically):

class C: A {
   var item: String {
       return "C"
   }
   override var usedItem: String {
       return item
   }
}

let c = C()
c.item
c.usedItem

Now we get “C” for both calls, as desired, but having to add the override keyword just for this is ugly at best. What, then, if we did add a declaration for 'usedItem' to the protocol?

protocol P {
   var item: String { get }
   var usedItem: String { get }
}

extension P {
   var item: String {
       return "P"
   }
   var usedItem: String {
       return item
   }
}

class A: P {
}

let a = A()
a.item         // returns “P"
a.usedItem // returns “P"

No surprises here.

class B: A {
   var item: String {
       return "B"
   }
}

let b = B()
b.item         // returns “B”
b.usedItem // still returns “P” !!

The result is still "P", even though 'usedItem' is now being dynamically dispatched. Yes, B doesn't specialise the protocol extension's implementation of 'usedItem' so there really isn't an implementation of that to dynamically dispatch but =that shouldn't matter= (yet it does). B does specialise 'item', though, so *that* should be dynamically dispatched (since it is declared in the protocol). And it is, if called directly, but if it's called from within the protocol extension's default implementation of another dynamically dispatched invocation that isn't itself specialised, then it's not.

Of course, there's a solution similar to that of class C but, like that solution, this one also involves the presence of extra code that is syntactially identical to the default implementation.

class C: A {
   var item: String {
       return "C"
   }
   var usedItem: String {
       return item
   }
}

let c = C()
c.item         // returns “C”
c.usedItem // now returns “C”, as desired.

What I find most surprising is that the static or dynamic dispatch behaviour of a method in the protocol extension is not determined solely by the absence or presence of its declaration in the protocol but also by whether or not conforming types actually have a specialised implementation. As I pointed out above, that second bit should not matter, yet it does. It seems to me that this is a bug in how protocol extensions work but I might be wrong about that.

Any thoughts?

Thanks.
Wagner


More information about the swift-users mailing list