9
votes

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?

2
I think that you are going to run into issues because you have two impls that look like they can overlap, even though the associated types prevent it from actually happening. I'd probably try implementing {a, the} trait for your two concrete types and then implement it for Counter<T> where T: Counted and delegate.Shepmaster
@Shepmaster: I think that's an answer?Matthieu M.
I would be interested in seeing an example of what you mean @Shepmaster - I think I understood what you meant but an example would be great.Simon Whitehead
@Shepmaster I took your recommendation and implemented Counted for both PlainCounter and AtomicCounter and then implemented Counter<T> where T: Counted where the calls on Counter just delegate to its counter field and everything worked out fine. I did some minor tweaking to the example to get it more in line with this new approach, example is here if ypu're interested.jeromefroe

2 Answers

10
votes

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() {}
2
votes

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.