15

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?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
jeromefroe
  • 1,345
  • 12
  • 19
  • 2
    [Smaller example](http://play.integer32.com/?gist=00e9ae6747f1dbb01df4070e34591732&version=stable). – Shepmaster Nov 03 '16 at 02:24
  • 1
    I think that you are going to run into issues because you have two `impl`s 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 where T: Counted` and delegate. – Shepmaster Nov 03 '16 at 02:29
  • @Shepmaster: I think that's an answer? – Matthieu M. Nov 03 '16 at 07:39
  • 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 Nov 03 '16 at 10:03
  • @Shepmaster I took your recommendation and implemented `Counted` for both `PlainCounter` and `AtomicCounter` and then implemented `Counter 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](https://play.rust-lang.org/?gist=e091e5ceb783c67a1d36bb89c2679b54&version=stable&backtrace=0) if ypu're interested. – jeromefroe Nov 03 '16 at 15:25

2 Answers2

15

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() {}
paholg
  • 1,910
  • 17
  • 19
  • I'm finding it a little unclear — how do we actually implement functions in the inner trait which refer to `self`? It'd appear `self` would be e.g. `(T, u8)`, but presumably since we're implementing `Deref` we don't actually have a concrete `u8`. – Asherah Apr 22 '17 at 08:15
  • Figured it out: give `InnerFoo` a type variable ``, then the trait functions' first parameter can be `t: T`. Add `` to the `(T, …) : InnerFoo` constraint on Foo. Then call `<(T, ::Target) as InnerFoo>::my_fn(self, …)`. Finally, impls of InnerFoo need the additional constraint `where T : Deref`. Then you can call `t.deref()` in that impl. – Asherah Apr 22 '17 at 08:28
  • You can also drop the first `T` in the tuple types this way. – Asherah Apr 22 '17 at 08:37
  • How this would look like for a trait like `AsRef`? – cdecompilador Feb 01 '22 at 16:57
  • `AsRef` doesn't have an associated type, so this pattern doesn't apply. – paholg Feb 02 '22 at 17:26
4

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.

trent
  • 25,033
  • 7
  • 51
  • 90
Ten
  • 1,283
  • 9
  • 12