I’ve been reading through the discussions of trait specialization and negative bounds (particularly RFC #586). I am working on a library which impls a spec which would require some kind of negative trait bound to be implemented correctly (the spec in question is the transit format; the issue concerns the division between scalar and composite types).
I think I have a solution to the problem presented in the prior RFC discussion, & I’d like to hear feedback on its feasibility:
- For every trait
Foothere exists a ‘negative trait’
!Foo, which has no methods or associated types.
!Foois implicitly defined whenever
Foois defined (and cannot be explicitly defined), and is imported whenever
Foois imported, but it is not implemented implicitly when
Foois not; it must be explicitly implemented.
!Fooare mutually exclusive. It is a coherence violation for a type to impl both
!Foo as a “real trait” like this, the semantics of negative bounds become much simpler and much less dangerous: the negative bound
T: !Foo does not mean that
T does not impl
Foo, but that it impls
!Foo. Thus, adding new trait impls to types is not a backwards compatibility hazard. In fact, negative traits work exactly the same way as positive traits: adding an additional bound is incompatible, removing a bound is not; removing an impl is incompatible, adding one is not.
Negative impls and bounds could be added to the std library and associated libraries where two traits are not intended to cohere in a single type. To take examples from the the num crate,
Integer could be bound
Float could be bound
!Integer, while the
Unsigned marker trait could be removed and types which would impl it could instead impl
!Signed. std library types like
Vec<T> could impl
!Num, given that they are not numeric types.
Introduing this mutual exclusivity would enable newly defined traits in third party libraries to piggy-back on standard traits. As a trivial example, a crate introducing additional mathematical functions with the trait
Foo could now
impl<T> Foo for T where T: Integer and
impl<T> Foo for T where T: Float, and there would be no coherence conflict, because it would be incoherent for a trait to simultaneously impl those two traits according to their bounds.
This ability to implement blanket impls for different traits is the primary benefit of this change, though it is also has the subsidiary benefit of enabling the definition of traits as exclusive for the purpose of ensuring that client programs do not implement both traits for a single type, which would be logically incoherent but not restricted by the type system at present (such as