The Problem
As far as I understand, one of the main reasons why !Trait
is disallowed,
is because it implies that implementing a trait would be a breaking change.
For example, let's say I'm using a trait Foo
from an extern crate:
use foo::Foo;
impl<T: !Foo> Bar for T {
\\ ...
}
let bar: &dyn Bar = &"hello" // doesn't impl foo
If later, the owner of foo
implemented Foo
for &'static str
, then my code would break.
That is because &'static str
would no longer be Bar
.
This means that implementing traits would be a breaking change.
So to avoid this, we don't allow negative implementations.
A Possible Solution
The way I would solve this issue, is not to forbid !Trait
,
but to forbid negative implementations without their complementary counterparts.
So the previous example would not compile, unless I implemented impl<T: Foo> Bar for T
.
Thus, T
has to be Bar
regardless of whether it implements Foo
.
Also, if you think about it, impl<T: !Foo> Bar for T
has no real expressivity by itself.
It is only useful once you also implement impl<T: Foo> Bar for T
,
so Rust can distinguish between two different implementations.
I think this would be a reasonable restriction,
since writing impl<T: !Foo> Bar for T
without its complement is no more useful than impl<T> Bar for T
.
This should also work with trait arithmetic. So you should be able to do something like:
impl<T: A + !B> Foo for T {
// ...
}
// required by the previous impl block
impl<T: A + B> Foo for T {
// ...
}
Drawbacks
As you increase the number of negative traits,
the number of implementations required to satisfy this rule increases exponentially.
Specifically, the number of separate implementations is 2^n
, where n
is the number of negative traits.
Also, it seems to be that some details about how to implement !Traits
haven't really been fully worked out yet.