Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 314
jeromefroe Avatar asked Nov 03 '16 01:11

jeromefroe


2 Answers

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() {}
like image 85
paholg Avatar answered Oct 31 '22 04:10

paholg


Unfortunately this is not implemented in the language yet.

There's this tracking issue: rust-lang/rust#20400.

An RFC rust-lang/rfcs#1672 was also proposed to solve this problem but was then postponed waiting for Chalk integration which will make it easier to implement.

In the meantime, you'll have to use the workaround proposed above.

like image 39
Ten Avatar answered Oct 31 '22 05:10

Ten