How does Swift disambiguate method calls?

Hey folks, take the following code as an example:

protocol MyProtocol {}
extension MyProtocol {
    func greeting() -> String {
        return "hello"
    }
}

protocol MyChildProtocol: MyProtocol {}
extension MyChildProtocol {
    func greeting() -> String {
        return "cheers"
    }
}

struct MyStruct: MyChildProtocol {}
let x = MyStruct()

print(x.greeting())

Both MyProtocol and MyChildProtocol implement the greeting() function. This code will print cheers and coming from Java that kind of makes sense to me. The "child protocol" takes precedent the "parent protocol" which is similar to how inheritance in Java works.

My question is: where is this documented? I'd like to read the full rule set that Swift uses to disambiguate functions in protocols when it might not be clear to a newbie like me which function should be called. Without knowing too much about how things work, I might have expected the override keyword to be required here.

I’m pretty sure nobody understands the actual rules, as evidenced by these two threads:

Need Help Understanding Protocols and Generics
Unusual Case of Generic Type-Inference

In principle, when dealing with a protocol conformance, at the point where a type is declared to conform to a protocol, witnesses for all requirements of that protocol are selected for the type, by ranking all visible overloads and choosing the most specific available implementation for each one.

However in practice when generics and class inheritance and protocol refinements become involved, unexpected behaviors emerge which I still have not seen explanations for, as described in the two threads linked above.

1 Like

This behavior is document in The Swift Programming Language guide: https://docs.swift.org/swift-book/LanguageGuide/Protocols.html under the section " Providing Default Implementations"

If a conforming type provides its own implementation of a required method or property, that implementation will be used instead of the one provided by the extension.

Edit: I misread the example - I thought greeting() was a requirement of the protocol, sorry! I'm not sure if the behavior of (non-required) methods in protocol extensions are covered in that section or a different one.

2 Likes

In this case, greeting() is not a requirement of the protocol and thus is statically dispatched. Therefore the greeting() on MyChildProtocol is a completely unrelated function that shadows the one on MyProtocol. The reason that version gets called is because MyChildProtocol is more specific than MyProtocol.

You can see this at work here: https://godbolt.org/z/171hKT

5 Likes

That seems to be my experience so far. Like I came up with another example that I wanted to test, this time similar situation but with a class and a protocol:

protocol MyProtocol {}
extension MyProtocol {
    func greeting() -> String {
        return "hello from protocol"
    }
}

class DummyParentClass {
    func greeting() -> String {
        return "hello from class"
    }
}

class DummyChildClass: DummyParentClass, MyProtocol {}

let x = DummyChildClass()

print(x.greeting())

Based on what you said about specificity, this code predictably prints hello from class since a concrete type like a class is "more specific" than a protocol (I guess?), but I wish there were someone who could point me to some place in the language spec that explains this behavior.

Seriously question: "Why" is the swift.org website and documentation (https://docs.swift.org/swift-book/LanguageGuide) not open source and on GitHub? :)
This would allow other developers to faster fixing unclear/missing documentation, like in practice https://github.com/JetBrains/kotlin-web-site and maybe adding more content to the current "empty" website

Edit: created a new topic in site-feedback

1 Like

One big thing to watch out for is that when you have MyProtocol: OtherProtocol you don't really have inheritance like with classes. Protocols behave differently. That's actually independent of this specific issue, which is a subtlety in protocol and extension definition. I think this is all already explained above but maybe one more explanation won't hurt. I hope I have this all correct, please let me know if I am wrong:

The simple explanation is that there's a difference between

protocol MyProtocol {}
extension MyProtocol {
  func f() {print("F")}
}

and

protocol MyProtocol {
  func f()
}
extension MyProtocol {
  func f() {print("F")}
}

In the first one f is just a method added in an extension. Anything that implements MyProtocol gets the f method from the extension but that's all. If they add their own f then it gets used. This is statically dispatched--the compiler knows which f to call and just implements it as a direct call.

In the second one f is a requirement of the protocol. If you leave it out of something that implements the protocol it won't compile, but the extension adds it in so that f is a default implementation. If you add your own f in an implementing class it will use that one but will dispatch dynamically.

A lot of the time this behaves identically like in most of these examples. But you get precise control over how it works--you can provide either a statically dispatched default implementation (f in the extension only) or a dynamically dispatched default implementation (f in the protocol and in the extension).

This is documented in The Swift Programming Language but it's not emphasized enough maybe.

2 Likes
Terms of Service

Privacy Policy

Cookie Policy