How to define trait used for enhancing tuples

I want a trait to retrieve Ref from Cache. It’s easy to retrieve Ref<T> from Cache.

impl<T> Foo for T {
    fn foo(…) -> Ref<'_, Self> …
}

Then, I want to retrieve (Ref<T1>, Ref<T2>, ) from Cache, which leads me to use macro to achieve the goal.

Then I should implement the trait for (T1, T2,), and return (Ref<T1>, Ref<T2>, ). I certainly need associated type to claim type Refs = (Ref<T1>, Ref<T2>, ) or directly claiming it like Foo<(Ref<T1>, Ref<T2>, )>.

#![allow(unused)]

use std::marker::PhantomData;

trait Foo<T> {
    // just use &Self instead of &Cache
    fn foo(&self) -> T;
}

struct Ref<'a, T> { _marker: PhantomData<&'a T> }

impl<'a, T> Foo<Ref<'a, T>> for T {
    fn foo(&self) -> Ref<'a, T> { 
        todo!()
    }
}

impl<'a, T> Foo<(Ref<'a, T>,)> for (T,) {
    fn foo(&self) -> (Ref<'a, T>,) { 
        todo!()
    }
}

trait Bar<'a> {
    type Ref;
    fn bar(&self) -> Self::Ref;
}

impl<'a, T: 'a> Bar<'a> for T {
    type Ref = Ref<'a, T>;
    fn bar(&self) -> Self::Ref {
        todo!()
    }
}

// Err conflicting implementations
impl<'a, T:'a> Bar<'a> for (T,) {
    type Ref = (Ref<'a, T>,);
    fn bar(&self) -> Self::Ref {
        todo!()
    }
}

(Playground)

Above, to implement the trait with macro, I certainly should choose Foo instead of Bar’s associated type. Am I right or there’s better way?

For this purpose, the associated type is better. It informs the type system that there is exactly one Ref version of any type, not possibly more than one; this results in more helpful type inference, among other things.

Another way to look at it is that a trait having an associated type is a function from types to types; this one answers “what is the Ref-using version of this type?”

1 Like

But how can I overcome the error(conflicting implementations) when using associated type?

Ah. No. You cannot have a blanket implementation of that kind.

You can use the type parameter version (but you may find this makes it difficult to actually use the trait), or you can implement the trait only for tuples (and use 1-element tuples where you would have used that blanket implementation).

1 Like

So I need one trait for T and another for tuples of T?

If the first trait would only have the one implementation impl<'a, T: 'a> Bar<'a> for T then you don't need a trait at all. (Unless you intend it to be an “extension trait” — I don't care for using those just to make one thing into a method.)

Thanks a lot.

Here is the actual trait I want to improve. I’ll try with associate type then.

Here's an example of using the type parameter approach to allow the blanket implementation. The result is that all tuples implement the trait for two different parameters, making the call sites ambiguous.

You could try to put the parameter somewhere to overcome the ambiguity, but it's still somewhat unergonomic.


Incidentally:

Your OP playground is confused with regards to lifetimes.[1] You probably want a GAT.[2] I'd also suggest #[deny(elided_lifetimes_in_paths)] so that the borrows in your return types are more visible.


  1. E.g. creating them out of nowhere. ↩︎

  2. alternatives include a lifetime on your trait, or making the methods take self and implementing the trait for references ↩︎

1 Like

This looks like combine Foo and Bar in my example together to avoid Bar’s conflict implications.

And thank you for teaching me this.

Here is the result. It's not possible to impl the completely same trait for T and tuple T maybe.

It is not, because then the tuple would have two implementations of the same trait, which is not allowed. Until/unless we get specialization anyway.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.