An Implementation Model for Rational Protocol Conformance Behavior

Well, the change moving X instances between modules that have different notions of Equatable for X, came long after there were “different definitions of a protocol conformance running around for a type.” So did I state the rule correctly above, or is it really don't move instances between modules (or presumably, files) that have conflicting conformances for those instances? I really wanna know.

Note that in this case:

  • C added its conformance in an update, long after D was already importing C. Did D break the rules of the programming model by allowing C to be updated?
  • D used code completion to find doSomething() on uniqueXs, and read all the documentation that popped up when they did that. Did D break the rules of the programming model by not tracking down the source of the symbol and noticing that it came from C and that C had added a conflicting conformance?

Yep, gloriously straightforward, right?

Also, true: as the compiler, you do never know whether a conformance is holding up some invariant. I hope we can all agree that upholding invariants is fundamental in supporting the creation of large, reliable systems. A huge part of this burden necessarily falls on humans, who must document and read API contracts. But if the language rules make it possible for a type to mean two different things and doesn't prevent me from mixing those things up, it undermines the whole effort. Note that even languages like Python, that have no static checking, don't have this issue: a type only ever means one thing at a time.

You may remember, from C++, that these can be the worst kinds of problems.

I consider the collateral damage of your solution---that the mere presence of a conflicting conformance anywhere poisons a type completely---to be greater than the problem you're trying to solve.

Really, you'd take "it's going to bite you eventually“ over “you have to put some code in separate files to disambiguate?”

Static type systems do not catch all bugs, nor should they. One hard thing to balance in a statically-typed language is when to back off on the type system, because the cost of appeasing the type checker exceeds the benefits. Having an unrelated conflicting conformance make a library type unusable creates busywork and frustration for users.

You know I'm well aware of these tradeoffs, which is part of why we still don't have static constraints on which errors can be thrown in Swift. I also am painfully aware of where we've been too enthusiastic for static checking, which is why I have to sprinkle try liberally through code where the sources of errors are irrelevant (if the effect on serialization code is annoying you should see what it does to a BGL-style breadth-first search or just about anything that uses lots of closures—so much harder to ignore the noise when it isn't all stacked uniformly at the beginning of every line). The reasons this is different are:

  • Unlike error propagation, conflicting conformances are extremely rare
  • When not extremely carefully managed, as you say they're “going to bite you eventually.“
  • The contexts they'll bite you in are unlimited, rather than just areas where you need to be careful anyway (where invariants are being broken).
  • Moving code into a separate file where the conformances are unambiguous is a relatively light burden that clarifies, rather than obfuscating.

Edit full disclosure: until I saw @mattrips' post yesterday I had forgotten that files in the same module (supposedly?) have their own sets of imports which should be hidden from one another, so one could argue that my simple rule about not mentioning X in a context where its conformances are ambiguous is incomplete. We should consider preventing X from being exchanged between files in the same module having conflicting conformances. I can see how that might be considered overly protective for individual module developers, but it might be necessary to support larger teams working on the same module; the implications should be thought through carefully.

Note that this situation we can create today has all the same implications as having scoped conformances (except that only scoped conformances let us reduce code bloat).

2 Likes