1
votes

I have a broad base trait. Some types only care about subset of its functionality, so I added a subtrait requiring user to implement smaller set of functions.

This code fails:

trait Base<T> {
    fn foo(arg: bool);
}

// Ext is a narrowing of Base requiring user to provide alternative, simpler interface for the same functionality
trait Ext<T>: Base<T> {
    fn bar();
}

// implement Base<T> for all types implementing Ext<T>
impl<T, E> Base<T> for E
where
    E: Ext<T>,
{
    fn foo(arg: bool) {
        Self::bar();
    }
}

struct Data<T>;

// error[E0119]: conflicting implementations of trait `Base<_>` for type `Data<_>`:
impl<T> Base<T> for Data<T> {
    fn foo(arg: bool) {}
}

With following error:

error[E0119]: conflicting implementations of trait `Base<_>` for type `Data<_>`:
  --> src/lib.rs:22:1
   |
11 | / impl<T, E> Base<T> for E
12 | | where
13 | |     E: Ext<T>,
14 | | {
...  |
17 | |     }
18 | | }
   | |_- first implementation here
...
22 |   impl<T> Base<T> for Data<T> {
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Data<_>`
   |
   = note: downstream crates may implement trait `Ext<_>` for type `Data<_>`

Interestingly, it works when I remove generality over T:

trait Base {
    fn foo(arg: bool);
}

// Ext is a narrowing of Base requiring user to provide alternative, simpler interface for the same functionality
trait Ext: Base {
    fn bar();
}

// implement Base for all types implementing Ext
impl<E> Base for E
where
    E: Ext,
{
    fn foo(arg: bool) {
        Self::bar();
    }
}

struct Data;

// works just fine
impl Base for Data {
    fn foo(arg: bool) {}
}

Some other posts on stackoverflow mentioned similar problems, but they generally have issues with foreign traits (the one from standard library). In my case both trait and type are local, therefore orphan rules should not kick in as far as I understand.

Basically, error mentions that downstream crates may implement trait 'Ext<_>' for type 'Data<_>', which isn't true because both Ext and Data would be foreign for those crates.

To summarize, my questions are:

  1. Why my blanket impl is rejected even though it doesn't seem to be possible for other crates to create a collision.
  2. Why version without T is not rejected, even though its mostly the same blanket impl?
  3. Is there any workaround for this issue?
1
It looks like your question might be answered by the answers of “conflicting implementations for trait” when trying to be generic; Conflicting implementations of trait in Rust; Resolving trait implementation conflicts; .... If not, please edit your question to explain the differences. Otherwise, we can mark this question as already answered.Shepmaster
Interesting read. Orphan rules is quite a deep rabbit hole. Updated my question.FLashM

1 Answers

1
votes

The reason you're seeing the error is that there is a possible collision between impl Base for E and impl Base for Data

these are both generics, so in theory I could create my own struct and implement the trait Base<T> as well as the trait Ext<T>. If I do this, your code would create duplicate implementations for Base<T>::foo since Both impl blocks are implementing Base<T>.

When you remove the T from your code it becomes more specific. You would implement Base for E and Base for Data. You can see the same error in your own second code example if you add the following

impl Ext for Data {
    fn bar() {}
}

It's the same basic message. The only difference is your first example only provides the possibility of a collision whereas the second (with my addition) actually causes the collision.

As for workarounds... Rust traits are not really inheritance so I would say the rusty way to do this would be to have separate traits for each subset of functions you want instead of trying to create a hierarchy of traits.

If your program really needs to have the inheritance type traits, then avoid using generics or bound them to concrete types.