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?
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() {}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With