Iterators over an owned Vec of Trait objects


#1

I’ve just spent hours trying to figure out something rather simple and I must be missing something very obvious:

I have a struct with a field notifiers: Vec<Box<dyn Notifier>>.

Now I would like to write an iterator that goes thru the Vec, looks up some data and returns a references to some of those Notifier objects (along with some other data – it basically checks whether it eligible for a contact).

Now, if I pass a reference of notifiers into the iterator, I run into lifetime issues immediately: self.notifiers does not live long enough, or cannot infer an appropriate lifetime due to conflicting requirements…I’ve seen them all. :see_no_evil:

So an obvious (albeit not very efficient) solution would be to just clone the Vec. But of course, trying to make the Notifier trait Clone, leads to the infamous cannot be made into an object because Clone is also Sized.

So I can’t hold a reference to it and I also can’t clone it… :exploding_head:

I’ve re-read the chapters on traits, iterators, & lifetimes in all my Rust books, but something is blocked in my head. What would be the right approach for a problem like that?


#2

Can you give a complete example of what you’ve tried along these lines? Ideally, give us code we can experiment with on the playground. I would expect it to have something like this:

struct IterFoo<'a> {
    notifiers: &'a [Box<dyn Notifier>],
}

#3

little typo there so as to not confuse anyone: s/trait/struct

Here is a fleshed out (but contrived) example. But I agree with @cuviper that you should provide a playground example that demonstrates your problem if it’s unclear to you after this example.


#4

First, an apology: I know it’s super bad form to post questions without a posting an SSCCE and I felt bad about it. It was just the end of the day, I spent hours trying to make it work and I was refactoring which meant my code was a huge mess, completely inadequate to be posted anywhere.

That said, I got it working and it seems like my biggest problem was actually one character. Your examples gave me some faith in my own code, because I did it similarly (I’ve implemented next() recursively but otherwise my code is quite similar) so thank you very much for taking the time – it helped me to look for errors elsewhere!

In the end my problem was this method:

 impl PhoneBook {
    fn notifiers_for_name_iter(&self, name: &str) -> MyIterator {
        MyIterator {
            contacts: self.book.get(name).expect("missing contact"),
            notifiers: &self.notifiers,
            pos: 0,
        }
    }
}

IOW I wanted to create the iterator within the object that owns the notifiers. And the problem was…I forgot the & before self in the fn line. :see_no_evil: This lead the borrow checker to yell at me and somehow intellectually I was convinced that my problem is that the problem is that the resulting MyIterator may live longer than self. So yeah the compiler messages weren’t very helpful and sent me down a rabbit hole. While I do understand why the self has to be a reference, I still haven’t 100% conceptualized what was happening, I’ll re-read a bunch of books I guess.

Thanks everyone!


#5

Fixed, thanks. :sweat_smile:


#6

So I have a follow-up question, this time with a little more code. The whole module can be found at https://gist.github.com/hynek/7e1ccf3d8dfc8b93da73f8c900398bce – unfortunately I can’t give you a playground since it uses the failure crate that doesn’t seem to be supported.

What I’m interested is the new (IMHO more idiomatic?) creation of the iterator:

impl PhoneBook {
    /// Iterator of notifiers for name, sorted by preferability.
    fn notifiers_iter(&self, name: &str) -> impl Iterator<Item = LePoke> {
        let contacts = self.book.get(name).expect("missing contact").clone();
        self.notifiers.iter().filter_map(move |notifier| {
            if let Some(contact) = contacts.get(&notifier.get_name()) {
                Some(LePoke {
                    notifier: notifier,
                    contact: contact.clone(),
                })
            } else {
                None
            }
        })
    }
}

This works, but the contact.clone() irritates me so I presume I’m doing something wrong™. Is there a more idiomatic way that doesn’t clone everything all the time?

Another approach that I like even more (the naming of LePoke in the other example illustrates my frustration of even naming that concept :slight_smile:) is to return closures (I tried before and failed but got it working this time):

impl PhoneBook {
    /// Iterator of notifiers for name, sorted by preferability.
    fn notifiers_iter<'a>(
        &'a self,
        name: &str,
    ) -> impl Iterator<Item = impl FnOnce(&'a str) -> Result<(), Error>> {
        let contacts = self.book.get(name).expect("missing contact");
        self.notifiers.iter().filter_map(move |notifier| {
            if let Some(contact) = contacts.get(&notifier.get_name()) {
                let contact = contact.clone();
                Some(move |text| notifier.notify(&contact, text))
            } else {
                None
            }
        })
    }
}

But again, the clone() and all the moves vex me…am I doing something wrong here? Having to put let contact = contact.clone(); into an own line also looks weird to me…


I’m also happy to accept general bike shedding on the linked Gist. :slight_smile: Overall I’m rather baffled on how much effort it takes to do basic operations on basic data structures (Vec/HashMap). In Python that’s be like 5 lines of code…am I complicating my own life by using dynamic dispatch?


#7

You want to borrow the contact from self, which means LePoke would need a lifetime parameter and borrow. Then, you need to express that the returned iterator borrows from self like so:

fn notifiers_iter<'a>(&'a self, name: &str) -> impl Iterator<Item = LePoke<'a>> + 'a { ... }

But the gist is you want the iterator have + 'a to indicate that it’s borrowing from self borrowed for the same lifetime.


#8

Sorry for being lazy, but could you point me to where this syntax is explained/Introduced? I know stuff like the plus sign when I’m describing boundaries in generics but having them standalone is something I don’t remember ever seeing? Is it a special case or am I just reading it wrong?


#9

To be honest, my impression is that impl trait docs are a bit scant now - I think I picked up on most of the things by seeing the various github issues around the impl trait feature.

You might be familiar with (trait) object bounds, ie Box<SomeTrait + 'a> or Box<SomeTrait>, which is Box<SomeTrait + 'static> in fully expanded form. You can think of impl trait being a parallel to that.


#10

Vitaly, thank you so much – a lot of dots were connected in this thread! It sent me on a tangent around lifetimes where I found another forum post of yours that helped me tremendously (type variance :|) so thank you very much for all the help!