Great post. One thing I wanted to note, because I think it’s important: in my series of posts on specialization, I was focused on a particular case: adding a blanket impl of Clone for all types that implement Copy. Or, more generally, adding a blanket impl of a supertrait in terms of a subtrait. Some examples:
impl<T: Copy> Clone for T
impl<T: Ord> PartialOrd for T
-
impl<T: Eq> PartialEq for T // this one doesn’t work because Eq lacks methods
But then I started to get greedy, and to wonder if we could make it legal to add any new impl (e.g., impl<T: Display> Debug for T), since supporting these kind of “bridge impls” was one of our original goals of specialization. I think what your post shows is that bridge impls are untenable: but implementing a supertrait in terms of a subtrait can still work.
To me this goes back to the zero-sum logic from “rebalancing coherence”: we allow child crates to (implicitly) use negative reasoning today relating to their local types. This implies then that parent crates can’t add an impl of an existing trait that may apply to existing types in a downstream crate, since they may already be relying on this negative reasoning.
But all is not lost. The supertrait case is pretty useful. And it seems like the Display/Debug thing was largely about convenience: we could probably still have a default impl that covered this case (if those were implemented), so that while you do have to opt in to implementing Debug, you don’t have to write out the body.