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
Foo there exists a ‘negative trait’ !Foo, which has no methods or associated types. !Foo is implicitly defined whenever Foo is defined (and cannot be explicitly defined), and is imported whenever Foo is imported, but it is not implemented implicitly when Foo is not; it must be explicitly implemented.
-
Foo and !Foo are mutually exclusive. It is a coherence violation for a type to impl both Foo and !Foo.
In reifying !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, and 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 String and 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 Signed and Unsigned).