user1885616
user1885616

Reputation: 83

How to implement an Iterator for a trait object

I am trying to create an Iterator for a trait that allows trait objects. My extremely simplified and contrived example would be something like this:

trait MyTrait<T>
where
    T: 'static,
{
    fn store(&mut self, t: T);

    fn access(&self, i: usize) -> T;

    fn iter(&self) -> MyIter<T> {
        MyIter::new(self)
    }
}

struct MyIter<'a, T: 'static> {
    collection: &'a dyn MyTrait<T>,
    j: usize,
}

impl<'a, T: 'static> MyIter<'a, T> {
    fn new(collection: &dyn MyTrait<T>) -> Self {
        Self {
            collection: collection,
            j: 0,
        }
    }
}

impl<'a, T: 'static> Iterator for MyIter<'a, T> {
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        self.j += 1;
        Some(self.collection.access(self.j))
    }
}

but this gives

error[E0277]: the size for values of type `Self` cannot be known at compilation time
  --> src/main.rs:10:21
   |
10 |         MyIter::new(self)
   |                     ^^^^ doesn't have a size known at compile-time
   |
   = note: required for the cast from `Self` to the object type `dyn MyTrait<T>`
help: consider further restricting `Self`
   |
9  |     fn iter(&self) -> MyIter<T> where Self: Sized {

Now, I understand that the compiler wants Self to be Sized and I could just add the where clause. However, I would like to implement MyTrait for other traits that are not Sized. In fact, as they need to be object traits, I cannot add Sized. How could I accomplish that.

Whatever I try, I run into a Sized problem. Is there a general solution or a tutorial for implementing Iterators in rust? Unfortunately, the only examples I have found are either iterators for structs or use unsafe code. As the Sized requirement is for the casting to dyn ... even boxing dyn ... won't be a solution as far as I can tell.

Upvotes: 2

Views: 200

Answers (1)

Chayim Friedman
Chayim Friedman

Reputation: 71075

The problem is that if Self is, for example, a trait object, then &Self is a wide pointer (two words), and we cannot convert it to trait object because we need more indirection for that (we don't have where to store the metadata).

A possible solution is to use generics instead of trait objects, but if you do want trait objects, you can use Box to create another indirection:

trait MyTrait<T>
where
    T: 'static,
{
    fn store(&mut self, t: T);

    fn access(&self, i: usize) -> T;

    fn iter(&self) -> MyIter<T> {
        MyIter::new(Box::new(IterRefAdapter(self)))
    }
}

struct IterRefAdapter<'a, U: ?Sized>(&'a U);

impl<U: ?Sized + MyTrait<T>, T: 'static> MyTrait<T> for IterRefAdapter<'_, U> {
    fn store(&mut self, t: T) {
        panic!("cannot call `store()` via shared reference");
    }

    fn access(&self, i: usize) -> T {
        U::access(self.0, i)
    }
}

struct MyIter<'a, T: 'static> {
    collection: Box<dyn MyTrait<T> + 'a>,
    j: usize,
}

impl<'a, T: 'static> MyIter<'a, T> {
    fn new(collection: Box<dyn MyTrait<T> + 'a>) -> Self {
        Self { collection, j: 0 }
    }
}

impl<'a, T: 'static> Iterator for MyIter<'a, T> {
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        self.j += 1;
        Some(self.collection.access(self.j))
    }
}

Upvotes: 2

Related Questions