How does Swift choose between 2 functions that are exactly the same but only different generic constraints?

Hi guys. Sorry about the confusing title.

Here is the situation.

I have a protocol defining two methods about encoding.

  • One for Codable objects:
func set<Object: Codable>(_ object: Object?, by key: Key) throws
  • The other one for NSObjects that are NSCoding:
func set<Object: NSObject & NSCoding>(_ object: Object?, by key: Key) throws

When I pass in an object that is an NSObject, Codable and NSCoding, it prefers the Codable function:

class Annoying: NSObject, Codable, NSCoding {
    func encode(with coder: NSCoder) { }
    required init?(coder: NSCoder) { }
    override init() { }

cache.set(Annoying(), by: "key")

My questions are

Why won't this cause ambiguity error?

Is this a defined behavior? Is this guaranteed in future Swift versions?

1 Like

It seems that the overload resolver favours a protocol composition (Codable is a type alias for Encodable & Decodable) better than a class + protocol composition. Because there is a case where it actually does favor one over the other, there's no ambiguity (at least in the eyes of the compiler).

As to why it is so: I believe that protocol composition is a stronger constraint because it is more likely to be enforced by the user (you can add protocol conformances at will, but can't change the superclass) — though somebody else might correct me here. Most likely there's a test case for such overload choices, so this behaviour is unlikely to change in the future, but if it is at all a concern to you, you might want to change the semantics of your functions (or give them distinct names or perform some additional type casting in the body of the function or whatever), since without the compiler you wouldn't at all be able to figure out which function gets applied just by reading the code, and I don't believe that you want your functions get resolved pretty much randomly.

You can use @​_disfavoredOverload to demote the first overload.

Do not do this. It is not a supported attribute and can break at any time. If you need to favor a particular overload in “tie-breaking,” consider creating a separate overload with a clearly more specific set of constraints (e.g., NSObject & NSCoding & Codable).

Terms of Service

Privacy Policy

Cookie Policy