Conflicting implementations despite negative_impl's

Why does the following code not work?

#![feature(negative_impls)]

trait A {}
trait B {}

impl<T: ?Sized + A> !B for T {}
impl<T: ?Sized + B> !A for T {}

trait Named {
    const NAME: &'static str;
}

impl<T: ?Sized + A> Named for T {
    const NAME: &'static str = "Apple";
}

impl<T: ?Sized + B> Named for T {
    const NAME: &'static str = "Banana";
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0119]: conflicting implementations of trait `Named`
  --> src/lib.rs:17:1
   |
13 | impl<T: ?Sized + A> Named for T {
   | ------------------------------- first implementation here
...
17 | impl<T: ?Sized + B> Named for T {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation

For more information about this error, try `rustc --explain E0119`.
error: could not compile `playground` due to previous error

I would argue, the two implementations can't overlap, because implementing A and implementing B is mutually exclusive.

Maybe this is the issue attempting to enable what I'm trying to do?

So I'd assume it's experimental yet, and not supported by nightly Rust?

Actually, I think it was explicitly ruled out by forbid conditional, negative impls · Issue #79098 · rust-lang/rust · GitHub

I wonder how this could work on its own.

If I try it with

fn assert_a<T: A>() {}
fn assert_b<T: B>() {}

fn main() {
    assert_a::<String>();
    assert_b::<String>();
}

it implements neither.

Should, say, String implement A or B?

I think neither impl<T: ?Sized + A> !B for T {} nor impl<T: ?Sized + A> Named for T { /* … */ } is related to auto-traits. But I don't really know. It's just a generic implementation, I would say.

This

doesn't imply that types implement one of A or B. It just implies that there is no type which implements both (but only none or one of them).

I see.

That'd be cool to have, actually. I can imagine some cases where I'd want to use that myself.

Inspired by @Yandros in this post, I could modify the example as follows:

#![feature(specialization)]

trait S {}

trait A: S {}
trait B: S {}

trait Named: S {
    const NAME: &'static str;
}

impl<T: ?Sized + S> Named for T {
    default const NAME: &'static str = "Apple";
}

impl<T: ?Sized + S + B> Named for T {
    const NAME: &'static str = "Banana";
}

fn print_name<T: Named>(_value: T) {
    println!("Name is {}.", T::NAME);
}

fn main() {
    struct Apple {}
    impl S for Apple {}
    impl A for Apple {}
    struct Banana {}
    impl S for Banana {}
    impl B for Banana {}
    let a = Apple {};
    let b = Banana {};
    print_name(a);
    print_name(b);
}

(Playground)

Output:

Name is Apple.
Name is Banana.

Errors:

   Compiling playground v0.0.1 (/playground)
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
 --> src/main.rs:1:12
  |
1 | #![feature(specialization)]
  |            ^^^^^^^^^^^^^^
  |
  = note: `#[warn(incomplete_features)]` on by default
  = note: see issue #31844 <https://github.com/rust-lang/rust/issues/31844> for more information
  = help: consider using `min_specialization` instead, which is more stable and complete

warning: `playground` (bin "playground") generated 1 warning
    Finished dev [unoptimized + debuginfo] target(s) in 1.01s
     Running `target/debug/playground`

The following compiles fine and without any warnings, thanks to using with_negative_coherence (since the semantics of negative impls being understood by coherence is precisely what you seem to be looking for, @jbe):

- #![feature(negative_impls)]
+ #![feature(negative_impls, with_negative_coherence)]

trait A {}
trait B {}

impl<T: ?Sized + A> !B for T {}
impl<T: ?Sized + B> !A for T {}

trait Named {
    const NAME: &'static str;
}

impl<T: ?Sized + A> Named for T {
    const NAME: &'static str = "Apple";
}

impl<T: ?Sized + B> Named for T {
    const NAME: &'static str = "Banana";
}
3 Likes

I'd like to cite myself :sweat_smile:

Thanks a lot!

4 Likes

It may not do what you want; you can still

impl A for () {}
impl B for () {}

But then you can't actually use the impls apparently (and bad things would probably happen if you found a way to). I.e. this feature is still wonky. (This probably deserves a dedicated issue.)

I haven't had time to play much, but here's some more reading for you. Also, historically, the ability to express mutually exclusive traits has led to putting things on hold until the feature of mutually exclusive traits can receive "more in-depth consideration".

Mentioned in passing previously.

2 Likes

:sob:

But I guess I shouldn't be surprised when using unstable features.

Is it really so bad? At least it catches the problem when I attempt to use the method.

The #![feature(specialization)] gives an explicit warning, which #![feature(negative_impls, with_negative_coherence)] does not.

This is my current use case: Playground.

The GAT AlignedRef<'a> allows me to return a reference to unaligned types such as &str but to return a clone of structures that require alignment using the Owned smart-pointer (as the stored data types are coming from LMDB where alignment isn't guaranteed).

I (supposedly) need the negative impl's and negative coherence to later implement Storable on tuples of StorableConstBytesLen types automatically, while being able to implement Storable on other tuples (e.g. tuples of smart pointers where the target is Storable) as well.

A workaround for me is to always declare a newtype for anything that I want to store and then implement Storable on that particular newtype and avoid the generic implementations altogether. I'm not sure what's worse: the extra effort to always implement Storable manually or using a half-baked feature.

Well, if I understand correctly, it will break when this lands:

1 Like

Ah okay. I think I should go the safe way then and avoid using it. Possibly the users of my library would then have more boilerplate code, but perhaps I can lessen the impact with macros. Or I implement certain things in my own library directly for certain concrete types instead of using traits.

Thanks for your warning and the (breaking) examples!

It should probably be marked incomplete, then :grimacing:

Yeah, I thought that too.