Workaround for conflicting implementation (negative trait bound)?

// -----------

struct A;
struct B;

enum Foo {
    A(A),
    B(B),
}

impl From<A> for Foo {
    fn from(val: A) -> Self {
        Self::A(val)
    }
}

impl From<B> for Foo {
    fn from(val: B) -> Self {
        Self::B(val)
    }
}


// -----------

// `R` should be `From`able from any of `Foo`'s `From` impls.
//  Except for `T`, which doesn't/shouldn't overlap with any of `Foo`'s `From` source types.
enum R<T: C> {
    Foo(Foo),
    Bar(T),
}


// The types that can be constructed into `R::Bar` must implement `C`.
trait C {}

struct C1;
impl C for C1 {}

struct C2;
impl C for C2 {}

// If `Foo` implements `From` for any T, that *does not* implement `C`
// should be `From`able for any `R<T: C>`.
impl<V /* : !C */, T: C> From<V> for R<T>
where
    Foo: From<V>,
{
    fn from(val: V) -> Self {
        Self::Foo(Foo::from(val))
    }
}

impl<T: C> From<T> for R<T> {
    fn from(val: T) -> Self {
        Self::Bar(B::from(val))
    }
}

fn main() {
    let a: R<C1> = R::from(C1); // ok -> R::Bar
    let b: R<C2> = R::from(C2); // ok -> R::Bar
    
    let c: R<C1> = R::from(A); // ok -> R::<C1>::Foo(Foo::A(A))
    let d: R<C1> = R::from(B); // ok -> R::<C1>::Foo(Foo::A(A))
}

Error:

error[E0119]: conflicting implementations of trait `std::convert::From<_>` for type `R<_>`
  --> src/main.rs:54:1
   |
45 | / impl<V /* : !C */, T: C> From<V> for R<T>
46 | | where
47 | |     Foo: From<V>,
48 | | {
...  |
51 | |     }
52 | | }
   | |_- first implementation here
53 | 
54 |   impl<T: C> From<T> for R<T> {
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `R<_>`

Playground: Rust Playground

I am trying to make the From impls for R work without any success. If I understand correctly I'd need negative trait bounds, however that doesn't exist in Rust as of now. Is there any workaround (even in nightly) that allows me to do that?

Blanket implementations of From usually run into this kind of trouble. I'd suggest just using a couple of appropriately-named constructors instead:

impl<T> R<T> {
    fn from_c(val: T) -> Self where T: C {
        // ...
    }

    fn from_v<V: Into<Foo>>(val: V) -> Self {
        // ...
    }
}

(imo this is better style even leaving aside the coherence problems, I think From impls should mostly only be used for really trivial/meaningless conversions)

4 Likes

I probably should’ve mentioned that the R type is used inside an Result<_, R>, so that the ? operator “automagically” calls the appropriate conversion method, so your approach wouldn’t work here sadly.

Well, there is no way to have both the From implementations you wrote, even (afaict) with the limited form of specialization that's available on nightly. You can keep one of them and insert an explicit conversion before ? when you want to use the other one.

That's what I am actually doing right now. I am using a wrapper type for the non transitive conversion and a macro to simplify the code overhead. However, I thought that I might be missing some tricks or nightly features that might help with this :confused:

even (afaict) with the limited form of specialization that's available on nightly.

I am curious. Do you know whether "full blown specialization" support would support this certain use-case? Or is this simply "out-of-scope" or just not possible?

You're trying to put a B in R<T>::Bar instead of a T. Typo?

Ignoring that, you can just implement From<T: C> A for R<T>, and B as well, manually. Non-generic impls on crate-local types are checked for conflict directly, i.e., negative reasoning is performed (so the generic implentation on T: C does not conflict). If doing so manually is too arduous, you can use a macro.

macro_rules! impl_from_for_r_via_foo {
    ($($t:ident),* $(,)?) => {$(
        impl From<$t> for Foo {
            fn from(val: $t) -> Self {
                Self::$t($t)
            }
        }
        
        impl<Other: C> From<$t> for R<Other> {
            fn from(val: $t) -> Self {
                Self::Foo($t.into())
            }
        }
    )*}
}

impl_from_for_r_via_foo!(A, B);

(You probably also want an implementation of From<Foo>, which I left out.)

That's my practical advice. The rest of this comment is about future possibilities.

Well, what you really wish you had here is a negative trait bound. But we better define what that means! In particular, taking T: !Trait to mean "T does not currently implement Trait" turns out to be a land-mine for backwards compatibility. If this was allowed, then if anyone downstream used such a bound on your struct, suddenly implementing the trait in question is a breaking change, through no fault of your own. It completely breaks the balance considerations of the orphan rules.

As far as I can tell, in this use case, you don't care about any of these upstream/downstream concerns, because it's entirely crate-local. But it's very unlikely for any changes in this area to be made that don't cover the general case, in my opinion.

So, could there be a way to maintain the balance? Yes, if the owner of a struct had the ability to say "I promise I'll never implement this trait (or if I do, it's a breaking change)", that could give downstream more flexibility without taking away from upstream.

In that case, T: !Trait would mean "T has explicitly opted out of implementing Trait". And as it turns out, the ability to opt-out does exist on nightly. (It's considered an extension of the unstable way to opt out of auto traits, which has existed for quite some time.)

So hope is in sight? Maybe with a telescope, but a lot will have to happen before this reaches stable, or is even actually useful to your use case on nightly:

  • Currently the negative impl just disallows the positive one
  • Coherence will need to be able to reason about negative impls
    • This basically means the current crate-local reasoning rules could be used in downstream crates
    • But you would still need an implementation for each type
    • So this doesn't really help your use case either
  • !Trait will have to be allowed as a where clause
    • Then you could have your generic negative-reasoning implementation
    • (Well, technically it's still positive reasoning IMO. Semantics I guess.)
  • All of this will take an RFC or two plus the normal implementation and waiting-for-stabilization time before reaching stable
    • On the up-side, there's an active interest in at least the coherence portion and the feature is at least partially implemented already

So I personally think it will eventually be possible, but is still a ways out. Read more here.

2 Likes

!Trait will have to be allowed as a where clause

Interesting. I was wondering if we even could go one step further and eliminate the manual !C impls by replacing it with a "conditional" one:

impl<T> !C for T where Foo: From<T> {}

impl<V: !C, T: C> From<V> for R<T>
where
    Foo: From<V>,
{
    fn from(val: V) -> Self {
        Self::Foo(Foo::from(val))
    }
}

impl<T: C> From<T> for R<T> {
    fn from(val: T) -> Self {
        Self::Bar(val)
    }
}

It seems like forbid conditional, negative impls · Issue #79098 · rust-lang/rust · GitHub wants to forbid "conditional negative impls", however Niko says: "[...] in other words, negative impls that do not apply to all instances of a type [...]". I am not exactly sure how to interpret that, but "instanced of a type" sounds like types like Box<T> (types with a generic type parameter). In my case however, that's not the case, as I am "negative impl"ing a T.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.