Byron
Byron

Reputation: 4316

How to use recursive associated types in functions?

I am trying to write a function that can descent into any kind of Value and inform a Delegate about the similarities it observes. The idea is to make this work across all kinds of Json/Yaml/YouNameIt values, generically.

Here is an MVCE to trigger the issue (playground link):

pub trait Value: PartialEq<Self> {
    type Item;
    type Key;
    fn items<'a>(&'a self) -> Option<Box<Iterator<Item = (Self::Key, &'a Self::Item)> + 'a>>;
}

pub trait Delegate<'a, V> {
    fn something(&mut self, _v: &'a V) {}
}

pub fn diff<'a, V, D>(l: &'a V, d: &'a mut D)
    where V: Value,
          <V as Value>::Item: Value,
          D: Delegate<'a, V>
{
    d.something(l);
    let v = l.items().unwrap().next().unwrap();
    d.something(v.1);
}

struct Recorder;
impl<'a, V> Delegate<'a, V> for Recorder {}

#[derive(PartialEq)]
struct RecursiveValue;

impl Value for RecursiveValue {
    type Key = usize;
    type Item = RecursiveValue;
    fn items<'a>(&'a self) -> Option<Box<Iterator<Item = (Self::Key, &'a Self::Item)> + 'a>> {
        None
    }
}

fn main() {
    let v = RecursiveValue;
    let mut r = Recorder;
    diff(&v, &mut r);
}    

When attempting to compile the code, the following error is produced:

error[E0308]: mismatched types
  --> <anon>:19:17
   |
19 |     d.something(v.1);
   |                 ^^^ expected type parameter, found associated type
   |
   = note: expected type `&'a V`
   = note:    found type `&<V as Value>::Item`

I'm trying to say that the associated Item type is of type V too. Is there a way to make such an algorithm work generically?

Upvotes: 1

Views: 417

Answers (2)

Shepmaster
Shepmaster

Reputation: 430290

Here's a further reduced example:

pub trait Value {
    type Item;
    fn items(&self) -> &Self::Item;
}

pub trait Delegate<V> {
    fn something(&mut self, v: &V);
}

pub fn diff<V, D>(l: &V, d: &mut D)
    where V: Value,
          V::Item: Value,
          D: Delegate<V>
{
    let v = l.items();
    d.something(v);
}

fn main() {}

The important thing to focus on are the restrictions on the generics of diff:

pub fn diff<V, D>(l: &V, d: &mut D)
    where V: Value,
          V::Item: Value,
          D: Delegate<V>

In words, this says:

  • V can be any type so long as it implements the Value trait.
  • V::Item can be any type so long as it implements the Value trait.
  • D can be any type so long as it implements the Delegate<V> trait.

Nowhere in that list of requirements was listed "V and V::Item must be the same". In fact, it's a feature that they are not required to be the same.

In this reduction, another solution would be to say D: Delegate<V::Item>. However, that wouldn't apply to the slightly larger reproduction:

pub fn diff<V, D>(l: &V, d: &mut D)
    where V: Value,
          V::Item: Value,
          D: Delegate<V::Item>
{
    d.something(l);
    let v = l.items();
    d.something(v);
}

As Matthieu M. has pointed out, you want to specify the associated type of the trait:

pub fn diff<V, D>(l: &V, d: &mut D)
    where V: Value<Item = V>,
          D: Delegate<V>

For further reading, check out Requiring implementation of Mul in generic function.

Upvotes: 1

Matthieu M.
Matthieu M.

Reputation: 299730

The answer lies at the very bottom of the Associated Types chapter of the Rust Book.

When using a generic type in a bound, as in V: Value, it is possible to constrain one or several of its associated types to specific types by using the Generic<AssociatedType = SpecificType> syntax.

In your case, it means constraining V to Value<Item = V>. This should also obsolete any reason to further constrain V::Item since the bounds to V are naturally available.


I do encourage you to read the book to help you learn Rust, or at least skim it to know what's available there and be able to refer to it when you have a difficulty.

Upvotes: 2

Related Questions