Weird "conflicting implementations of trait" error with only local items

pub trait MyFn<T> {}
impl<T, F, O> MyFn<T> for F where F: Fn(T) -> O {}

// Local is not MyFn nor is it Fn(T) -> O
pub struct Local<G>(G);

pub trait Guard<T> {}

impl<T, G> Guard<T> for G where G: MyFn<T> {}
impl<T, G> Guard<T> for Local<G> where G: Guard<T> {}
error[E0119]: conflicting implementations of trait `Guard<_>` for type `Local<_>`:
  --> src/lib.rs:12:1
   |
11 | impl<T, G> Guard<T> for G where G: MyFn<T> {}
   | ------------------------------------------ first implementation here
12 | impl<T, G> Guard<T> for Local<G> where G: Guard<T> {}
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Local<_>`

I don't get where the error (playground) comes from. Local<_> is not MyFn<_> so the impls shouldn't conflict. All traits and the Local type are local to the crate, so downstream crates shouldn't be able to break anything too, right?

P.S. this came out while experimenting with this playground.

The compiler doesn't care about what traits are implemented now. The type Local<G> could implement MyFn<T>, and that would cause a conflict.

It will only compile if adding more impls could not break the impls that already exist.

5 Likes

This feels too restrictive, especially since this is allowed:

// <Same Guard/MyFn decl impls>

// I could still impl MyFn<T> for Local and it will break everything
pub struct Local;
impl Guard<()> for Local {}

But I guess it's how it is...

Though why then this compiles:

pub struct Local<G>(G);

pub trait Guard<T> {}

impl<T, G: Fn(T) -> O, O> Guard<T> for G {}
impl<T, G> Guard<T> for Local<G> where G: Guard<T> {}

Is this any different than the case with MyFn? What is stopping me from making an Fn impl (but doesn't with MyFn)?

I think that this blanket implementation covers Local:

impl<T, F, O> MyFn<T> for F where F: Fn(T) -> O {}

This implements MyFn for all T which can be passed to a function and since there is no bound on T it's blanket.

Then when we implement Guard for G where G is MyFn we end up creating a blanket which utiltizes the blanket MyFn impl.

That's my interpretation of where the failure comes from.

It doesn't. It implements MyFn for all F which are functions. You can even test that Local doesn't impl MyFn.

Another crate is allowed to do the following:

struct Bar;

impl example::MyFn<Bar> for example::Local<Bar> {}
impl example::Guard<Bar> for Bar {}

which causes MyFn to be implemented for Local<Bar>. This would cause a conflict between your two Guard<T> implementations. Your test only checks that Local<()> doesn't implement MyFn, but it doesn't check that Local<T> doesn't implement MyFn for all types T.

Doesn’t the orphan rule prevent this? Neither MyFn nor Local are defined by the other crate.

Nope, I checked it and I can implement MyFn<Bar> for any type.

cat > example.rs <<EOF
pub trait MyFn<T> {}
impl<T, F, O> MyFn<T> for F where F: Fn(T) -> O {}

// Local is not MyFn nor is it Fn(T) -> O
pub struct Local<G>(G);

pub trait Guard<T> {}
EOF
cat > main.rs <<EOF
struct Bar;

impl example::MyFn<Bar> for example::Local<Vec<()>> {}
impl example::Guard<Bar> for Bar {}

fn main() {}
EOF
rustc example.rs --crate-type lib
rustc main.rs -L. --edition 2018 --extern example
1 Like

That definitely seems to go against the orphan rule description given in the Rust book:

One restriction to note with trait implementations is that we can implement a trait on a type only if either the trait or the type is local to our crate. For example, we can implement standard library traits like Display on a custom type like Tweet as part of our aggregator crate functionality, because the type Tweet is local to our aggregator crate. We can also implement Summary on Vec<T> in our aggregator crate, because the trait Summary is local to our aggregator crate.

But we can’t implement external traits on external types. For example, we can’t implement the Display trait on Vec<T> within our aggregator crate, because Display and Vec<T> are defined in the standard library and aren’t local to our aggregator crate. This restriction is part of a property of programs called coherence , and more specifically the orphan rule , so named because the parent type is not present. This rule ensures that other people’s code can’t break your code and vice versa. Without the rule, two crates could implement the same trait for the same type, and Rust wouldn’t know which implementation to use.

Maybe example::MyFn<Bar> is being considered local because Bar is local? This feels like it’s either a compiler or documentation bug, but I’m not sure which.

Yes, that's exactly what's happening here.

In the original example there is a blanket impl of MyFn<T> for F ("blanket" because T and F are uncovered type parameters) and the rules around blanket impls are somewhat different from other impls. std has no applicable blanket impls for Fn(T) and I believe this is the reason for the difference between MyFn<T> and Fn(T).

I always refer to RFC #2451 to work out coherence issues. I have not yet read it enough times to have the rules memorized.

2 Likes

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.