I just watched the WWDC24 "Explore Swift performance", which was excellent. One major surprise for me (at around 6:30) in the video is that a protocol conformance will always be dynamically dispatched, whereas an extension on that protocol will always be statically dispatched. This seems odd to me as I always thought the difference between a protocol and protocol extension was more semantic than an actual separation. Why does this different dispatch behavior occur? What's going on under the hood?
Because a protocol extension member isn't properly "part of" the protocol. It's not a protocol requirement, so its implementation cannot vary across different concrete types. For any protocol, you can look at its main body, and the members listed there will be the only members that are given different implementations by different concrete types.
For methods, this means they behave the same as top-level generic functions, and any "override" is effectively just an overload:
struct MyStruct {}
protocol MyProtocol {}
extension MyProtocol {
func myMethod(arg: Int) {
...
}
}
extension MyStruct: MyProtocol {
func myMethod(arg: Int) {
// An "override" that will only be called when
// the type is known to be `MyStruct` at the call site.
}
}
// behaves the same as...
func myFunction<T>(self: T, arg: Int) where T: MyProtocol {
...
}
func myFunction(self: MyStruct, arg: Int) {
// An overload that will only be called when
// the type is known to be `MyStruct` at the call site.
}