Search code examples
rustiteratorownership

Owning iterator in Rust?


I need to create an iterator that owns the value (allows to wrap the trait object to Rc) and return it as next() value (playground):

use std::rc::Rc;
use std::collections::HashMap;

trait TProduct {
    fn get_title(&self) -> String;
}

struct Product {
    title: String
}

impl<'a> TProduct for Product {
    fn get_title(&self) -> String { self.title }
}

trait TStorage<'a> {
    fn get_products(&self, key: &str) -> Option<Box<dyn Iterator<Item=Rc<dyn TProduct + 'a>> + '_>>;
}

struct Storage<'a> {
    products: HashMap<String, Vec<Rc<dyn TProduct + 'a>>>
}

impl<'a> TStorage<'a> for Storage<'a> {
    fn get_products(&self, key: &str) -> Option<Box<dyn Iterator<Item=Rc<dyn TProduct + 'a>> + '_>> {
        self.products.get(key)
            .map(|it| {
                let iter = it.into_iter(); // iter of &Rc, but we need owning iter (of Rc)
                let boxed_iter: Box<dyn Iterator<Item=Rc<dyn TProduct + 'a>>> = Box::new(iter); // problem here
                boxed_iter
            })
    }
}

fn main() {
    println!("Hello, world!");
}

I'm getting the following:

430 |                 let boxed_iter: Box<dyn Iterator<Item=Rc<dyn TProduct + 'a>>> = Box::new(iter);
    |                                                                                 ^^^^^^^^^^^^^^ expected struct `Rc`, found reference
    |
    = note: expected struct `Rc<(dyn TProduct + 'a)>`
            found reference `&Rc<dyn TProduct>`
    = note: required for the cast to the object type `dyn Iterator<Item = Rc<(dyn TProduct + 'a)>>`

The actual problem is that i have another TStorage impl (based on flatbuffers and owning a vec of TProduct) that returns Rc instances (not references), thus i need to adapt trait and this impl signatures too.

I do understand that current iterator borrows vec objects (and does not move them). Is it possible to return owning iterator? Can it be done with wrapper/adapter (that accepts ref to Rc and clones it)?

I've tried to adapt flatbuffers impl to return iterator of references, but it expectedly does not work as it can't hold temporary created objects:

&Rc::new(each_product)

I've been thinking about using Cow but i'm getting some other lifetime-related error output.

PS. I've tried cloning wrapper (that clones Rc and thus returns an instance, not a reference), but it does not work due to lifetimes (playground):

// converts `&Rc` into `Rc`
struct CloningWrapper<'a> {
    iter: std::slice::Iter<'a, Rc<dyn TProduct + 'a>>
}

impl<'a> Iterator for CloningWrapper<'a> {
    type Item = Rc<dyn TProduct + 'a>;
    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(|it| {
            it.clone()
        })
    }
}

impl<'a> TStorage<'a> for Storage<'a> {
    fn get_products(&self, key: &str) -> Option<Box<dyn Iterator<Item=Rc<dyn TProduct + 'a>> + '_>> {
        self.products.get(key) // problem here
            .map(|it| {
                let iter = it.into_iter();
                let wrapped_iter = CloningWrapper { iter };
                let boxed_iter: Box<dyn Iterator<Item=Rc<dyn TProduct + 'a>>> = Box::new(wrapped_iter);
                boxed_iter
            })
    }
}

due to the following:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/main.rs:40:23
   |
40 |         self.products.get(key)
   |                       ^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 39:5...
  --> src/main.rs:39:5
   |
39 |     fn get_products(&self, key: &str) -> Option<Box<dyn Iterator<Item=Rc<dyn TProduct + 'a>> + '_>> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that reference does not outlive borrowed content

PPS2. Having the same lifetime issue with Cow (playground)


Solution

  • As Stargateur notes, Iterator already has a built-in cloning adapter: Iterator::cloned. So you can just use that to get an Iterator<Item=Rc<_>>, then cast that to the relevant trait object:

    impl<'a> TStorage<'a> for Storage<'a> {
        fn get_products(&self, key: &str) -> Option<Box<dyn Iterator<Item=Rc<dyn TProduct + 'a>> + '_>> {
            self.products.get(key)
                .map(|it| {
                    Box::new(it.into_iter().cloned()) as Box<dyn Iterator<Item=_>>
                })
        }
    }
    

    That aside iter() versus into_iter() doesn't matter, your input is an &Vec, so you get a std::slice::Iter either way.