The compiler's not allowed to see whether the body of doSomething(_:)
uses Mixin, because maybe it does in this version of the MixinUser library, but doesn't in next year's release. Or vice versa. (When library evolution is enabled, changing the body of a function should not be an ABI-breaking change—maintaining ABI compatibility is already trickier in Swift than in C!)
I also agree with @Lantua that if you can't see a requirement in the declaration of a function, it's weird for the compiler to complain about it. Then again, there are all sorts of function preconditions in Swift that get deferred to runtime ("the upper bound of a Range cannot be less than the lower bound"), and it would actually be very nice if the compiler checked them, so I can't say that this is out of consideration.
Using different conformances in different contexts is a self-consistent answer to this problem, but a somewhat tricky one. Let's make this more concrete using Hashable. (I'm not explicitly separating things into separate modules this time, but imagine I did to make the problem harder.)
protocol A: Equatable {
var a: Int
}
protocol B: Equatable {
var b: Int
}
extension A: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(a)
}
}
extension B: Hashable { // typo fixed, thanks Lantua
func hash(into hasher: inout Hasher) {
hasher.combine(b)
}
}
struct Concrete: Equatable, A, B {
var a, b: Int
}
func makeSet<Element: A>(_ element: Element) -> Set<Element> {
var result = Set<Element>()
result.insert(element)
return result
}
func checkIf<Element: B>(_ element: Element, in set: Set<Element>) -> Bool {
return set.contains(element)
}
let value = Concrete(a: 1, b: 2)
let set = makeSet(value) // if this uses A: Hashable
let isPresent = checkIf(value, in: set) // and this uses B: Hashable
print(isPresent) // then this might print 'false'
I'm oversimplifying here; the Swift runtime, at least, would not consider these Sets to have the same type because they use different conformances. But you get the idea.
One answer to this problem is actually what you said:
Probably, to not spawn a crazy amount of modules if such a case has arisen, it is better to use namespacing, so a programmer can choose required conformance within a certain scope within a single module.
...but today the way to spell "choose a particular (or alternate) conformance" in Swift is "use a wrapper type". I do think it should be easier to define and use such types, but Swift isn't unique here; I don't know of any languages with generics that don't use wrapper types for this besides ML. (ML's approach says you always have to specify which conformance you want—there's no default—and so it's as if all your generic types used a Strategy or delegation pattern.)
We've been ignoring dynamic lookup, and that's because it's really worse than everything else. In the same example as before, what happens for (Concrete() as Any) as? Mixin
? (Keep in mind that the conversion to Any
might happen in a binary library we can't see.) Without any other context, there are two, equally valid options, and we'd have to decide carefully what happens, or whether such "mixin conformances" can only be used statically or something.
(Objective-C's answer, at the method level, is to pick one based on library load order, which is pretty inscrutable.)