genericsstructtypesrust

Conflicting trait implementations even though associated types differ


I'm trying to an create a generic struct which wraps an isize or an AtomicIsize, but I am running into an error when I try to implement a trait for both possible implementations of the struct. I created a minimal example which demonstrates my issue below.

use std::sync::atomic::{AtomicIsize, Ordering};
use std::ops::Deref;
use std::marker::PhantomData;

pub trait Counted {
    fn inc(&self, value: isize);
}

pub type PlainCounter = isize;
pub type AtomicCounter = AtomicIsize;


pub struct Counter<'a, T: 'a> {
    counter: T,
    phantom: PhantomData<&'a T>,
}

impl<'a, T> Counter<'a, T>
    where T: Deref<Target = PlainCounter>
{
    pub fn new(counter: T) -> Self {
        Counter {
            counter: counter,
            phantom: PhantomData,
        }
    }
}

impl<'a, T> Counted for Counter<'a, T>
    where T: Deref<Target = PlainCounter>
{
    fn inc(&self, value: isize) {
        self.counter += 1;
    }
}

impl<'a, T> Counter<'a, T>
    where T: Deref<Target = AtomicCounter>
{
    pub fn new(counter: T) -> Self {
        Counter {
            counter: counter,
            phantom: PhantomData,
        }
    }
}

impl<'a, T> Counted for Counter<'a, T>
    where T: Deref<Target = AtomicCounter>
{
    fn inc(&self, value: isize) {
        self.counter.fetch_add(value, Ordering::SeqCst);
    }
}

(playground)

The error I get is that the compiler found conflicting implementations of trait `Counted` for type `Counter<'_, _>`. It seems that the compiler cannot determine that the implementations are for two different types T, namely T: Deref<Target = PlainCounter> and T: Deref<Target = AtomicCounter>. Is there perhaps a way to provide additional information to the compiler so it can distinguish between the two cases, or am I on the wrong path entirely?


Solution

  • You can accomplish this pattern by defining a second trait that does the actual work, and is implemented for (Counter<'a, T>, <T as Deref>::Target), and have the Counter trait call out to that implementation.

    I don't think that was very clear, but I think an example can illustrate well. Using Shepmaster's shorter example for clarity, we would go from this:

    use std::ops::Deref;
    
    trait Foo {}
    
    impl<T> Foo for T
        where T: Deref<Target = u8>
    {}
    
    impl<T> Foo for T
        where T: Deref<Target = bool>
    {}
    
    fn main() {}
    

    to this:

    use std::ops::Deref;
    
    trait Foo {}
    trait InnerFoo {}
    
    impl<T> Foo for T
        where T: Deref,
              (T, <T as Deref>::Target): InnerFoo
    {}
    
    impl<T> InnerFoo for (T, u8)
    {}
    
    impl<T> InnerFoo for (T, bool)
    {}
    
    fn main() {}