Operating on collections of either boxed trait objects OR trait object references

I have a trait:

trait Get {
    fn get(&self) -> u32;
}

and some types that implement that trait:

struct Foo(u32);
struct Bar(u16, u16, u16);

impl Get for Foo {
    fn get(&self) -> u32 {
        self.0
    }
}

impl Get for Bar {
    fn get(&self) -> u32 {
        self.0 as u32 + self.1 as u32 + self.2 as u32
    }
}

There are two ways that these trait objects usually end up in a collection:

let mut v1 = Vec::<&dyn Get>::new();     // collection of trait object references
let mut v2 = Vec::<Box<dyn Get>>::new(); // collection of boxed trait objects

I want a function that can operate on generic collections of this trait object. I don't know if passing an iterator as an argument is the right approach here, but that's what I've been trying.

I can do either of these:

fn sum_from_iter_box<'a, I, T>(it: I)
where
    I: IntoIterator<Item = &'a Box<T>>,    // <-- Box<T>
    T: Get + 'a + ?Sized,
{ ... }

fn sum_from_iter_ref<'a, I, T>(it: I)
where
    I: IntoIterator<Item = &'a &'a T>,     // <-- &'a T
    T: Get + 'a + ?Sized,
{ ... }

But I haven't been able to figure out how to write a single function that can take an Iterator with either Item type.

Is this possible? Is there a better approach?

Playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5b581293fbef6d7032c363153c04676b

(Goal is to unify sum_from_iter_box and sum_from_iter_ref as a single function definition)

Related Reddit discussion:

1 Like
fn sum_from_iter_ref<T, I>(it: I)
where
    I: IntoIterator,
    I::Item: AsRef<T>,
    T: Get + ?Sized,
{ ... }

Only issue with this method is that type inference won't work for T so you will have to supply it manually.

You could make a new trait to generalize over this, but I don't think that is worth it.

I ended up using Diggsey's solution:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f99351f0388d3829d6f8731388d15df8

fn sum_from_iter<'a, I, T, U>(it: I)
where
    I: IntoIterator<Item = &'a T>,
    T: std::ops::Deref<Target = U> + 'a,
    U: Get + 'a + ?Sized
{
    let sum = it.into_iter().fold(0, |acc, get| get.get() + acc);
    println!("sum: {}", sum);
}
1 Like

Doesn't using AsRef<dyn Get> solve this issue ?

fn sum_from_iter<'a> (
    it: impl IntoIterator<Item = impl AsRef<dyn Get> + 'a>,
) -> u32
{
    it  .into_iter()
        .map(|x| x.as_ref().get())
        .sum()
}

Yes, but that also limits which iterators you can pass in. Using Deref as noted earlier is a better solution.