GHPR
GHPR

Reputation: 65

Conflicting implementation error that only appears when type parameters are introduced (and disappears with helper trait)

Below I define three traits. For each trait, I

In each case, however, I observe something slightly different.

Case 1

For the trait GetOneVer1 I observe no errors.

Case 2

For the trait GetOneVer2 I get an error:

conflicting implementations of trait `GetOneVer2<_>` for type `&usize`
downstream crates may implement trait `GetOneVer2<_>` for type `usize`rustcE0119

I think I understand this error -- the compiler is worried that in the future someone will implement the trait on usize and then that implementation will automatically extend to &'a usize because I implement the trait on &'a T where T implements the trait. That means we could potentially have two competing implementations of the same trait.

Case 3

For the trait GetOneVer3 I get no error. Note that this case is essentially identical to case 2. The only difference is that, in this case, I define an extra trait "InheritTrait" and require that T implements InheritTrait in my implementation on &'a T.

Questions

//  ===============================================================================
//  TRAIT 1: NO ERROR
//  ===============================================================================

// define trait
pub trait GetOneVer1 {
    fn get_one(&self) -> usize;
}

// implement on &'a T, where T implements the trait
impl<'a, T: GetOneVer1> GetOneVer1 for &'a T {
    fn get_one(&self) -> usize {
        1
    }
}

// implement on &'a usize
impl<'a> GetOneVer1 for &'a usize {
    fn get_one(&self) -> usize {
        1
    }
}

//  ===============================================================================
//  TRAIT 2: ERROR
//  ===============================================================================

// define trait
pub trait GetOneVer2<Key> {
    fn get_one(&self, index: Key) -> usize;
}

// implement on &'a T, where T implements the trait
impl<'a, Key, T: GetOneVer2<Key>> GetOneVer2<Key> for &'a T {
    fn get_one(&self, index: Key) -> usize {
        1
    }
}

// implement on &'a usize
impl<'a, Key> GetOneVer2<Key> for &'a usize {
    fn get_one(&self, index: Key) -> usize {
        1
    }
}

//  ===============================================================================
//  TRAIT 3: NO ERROR (CORRECTED BY ADDING A SECOND TRAIT REQUIREMENT)
//  ===============================================================================

// define "helper trait"
pub trait InheritTrait {}
// impl<'a, T: InheritTrait> InheritTrait for &'a T {} // uncommenting this line does NOT produce any errors

// define trait
pub trait GetOneVer3<Key> {
    fn get_one(&self, index: Key) -> usize;
}

// implement on &'a T, where T implements the trait
impl<'a, Key, T: GetOneVer3<Key> + InheritTrait> GetOneVer3<Key> for &'a T {
    fn get_one(&self, index: Key) -> usize {
        1
    }
}

// implement on &'a usize
impl<'a, Key> GetOneVer3<Key> for &'a usize {
    fn get_one(&self, index: Key) -> usize {
        1
    }
}

fn main() {
    println!("Hello, world!");
}

Upvotes: 1

Views: 236

Answers (1)

Chayim Friedman
Chayim Friedman

Reputation: 70860

Why does case 2 produce errors whereas cases 1 and 3 do not? The only difference I see is that, in case 2, the trait has a generic type parameter, and in case 3 there are both a generic type parameter and a "helper" trait.

The generic parameter is exactly the problem.

Who can implement GetOneVer1 for usize? Only you (who defined the trait) or the owner of usize (std).

Who can implement GetOneVer2<Key> for usize for some Key? You, usize's owner, or anyone who owns Key. And since Key is generic, that means everyone.

The orphan rules have not problems with a potential conflict between the owner of the trait and the owner of the type, because the compiler can see this impl does not exist (it depends only on upstream crates, std in this example). However, in the second case, downstream crates may implement it - and thus the compiler cannot prove this impl will not exist.

In the third case, we re-added the guarantee - as long as no impl InheritTrait for usize exists (which only you and std can provide), no trait can get the blanket implementation for references. Indeed, if you will add this impl, the compiler will error.

There's a commented line in case 3 that auto-implements InheritTrait on immutable references. Uncommenting this line produces no errors, and (I believe) achieves my ultimate goal of auto-implementing a trait on immutable references. Will this method (using a helper trait that has no methods or type parameters) work to bypass the problem posed by case 2, in general, or are there other subtleties I should worry about?

It will not work, because you still need impl InheritTrait for usize, and like we saw, this impl is impossible to write.

Upvotes: 2

Related Questions