3

Furthering the example of implementing IntoIterator for a wrapped vector as per the Rust book, I am also trying to implement IntoIterator for a reference to the wrapper, as per the following code (Playground link):

struct VecWrapper(Vec<i32>);

impl VecWrapper {
    fn iter(&'static self) -> Iter {
        Iter(Box::new(self.0.iter()))
    }
}

struct Iter(Box<Iterator<Item = &'static i32>>);

impl Iterator for Iter {
    type Item = &'static i32;
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next()
    }
}

impl IntoIterator for &'static VecWrapper {
    type Item = &'static i32;
    type IntoIter = Iter;
    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

fn main() {
    // let test = vec![1, 2, 3]; // obviously, works
    let test = VecWrapper(vec![1, 2, 3]); // not working
    for v in &test {
        println!("{}", v);
    }
}

Although the implementation compiles, the attempt to use it in main doesn't with the following error:

error[E0597]: `test` does not live long enough
  --> src/main.rs:31:14
   |
31 |     for v in &test {
   |              ^^^^^
   |              |
   |              borrowed value does not live long enough
   |              argument requires that `test` is borrowed for `'static`
...
34 | }
   | - `test` dropped here while still borrowed

This code is greatly simplified from what I would actually want to use as to using only 'static lifetimes, using an existing contained type, and using i32 for the inner (iterated) type, but it is boiled down to show just the problem.


The accepted answer solves the first part of the problem as to not using 'static and using + 'a with traits. I still am having a problem with the actual code, which is a LazyList implementation. I've posted that as Am I incorrectly implementing IntoIterator for a reference to a LazyList implementation or is this a Rust bug?.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
GordonBGood
  • 3,467
  • 1
  • 37
  • 45

1 Answers1

13

You have correctly implemented the iterator for references to VecWrappers that live for the entire length of the program — the 'static lifetime.

Chances are you want to have a generic lifetime. That lifetime will then be provided a concrete lifetime, unique to each instantiation. Usually, we are lazy and just give this lifetime the name 'a:

struct VecWrapper(Vec<i32>);

impl VecWrapper {
    fn iter(&self) -> Iter {
        Iter(Box::new(self.0.iter()))
    }
}

struct Iter<'a>(Box<dyn Iterator<Item = &'a i32> + 'a>);

impl<'a> Iterator for Iter<'a> {
    type Item = &'a i32;

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next()
    }
}

impl<'a> IntoIterator for &'a VecWrapper {
    type Item = &'a i32;
    type IntoIter = Iter<'a>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

fn main() {
    let test = VecWrapper(vec![1, 2, 3]);
    for v in &test {
        println!("{}", v);
    }
}

Important changes:

  • Box<dyn Iterator<Item = &'a i32> + 'a> - + 'a was added. This is needed because a trait object will assume that there are no interior values that reference anything with a short lifetime.
  • The Item type is now &'a i32.
  • A generic lifetime is declared in many places and provided in many others (<'a>).

See also:


Normally, there wouldn't be a reason to use a trait object here. I'd just embed the iterator directly:

struct Iter<'a>(std::slice::Iter<'a, i32>);

This avoids the need to have any indirection, which is unused in this case anyway. Additionally, it more obviously couples the lifetimes.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • I already knew how to implement using `'a` instead of `'static` as I mentioned in the question as to just simplifying and already had a version using `'a` in the same places as you do; what I missed was the `+ 'a` in the definition for the `Iter struct` (your first point). For that, I thank you very much. For others reading this, as @Shpmaster saids, using `'static` doesn't work here, even with the `+`, as the general use in an iteration/for loop will be inside another function/block. – GordonBGood Nov 18 '16 at 02:28
  • While I agree that your last suggestion may make the code clearer as to lifetimes, it requires a deep knowledge of the internals of the standard library and increases the complexity of initializing as in something like `std::slice::Iter { ptr: self.0[0], end: (self.0[0] as *const i32).offset(self.0.len() as isize), _marker: std::marker::PhantomData }`, which isn't possible since the `ptr`, `end`, and `_marker` fields are private and can't be initialized outside the module That's not usable, is it? – GordonBGood Nov 19 '16 at 00:35
  • @GordonBGood *it requires a deep knowledge of the internals of the standard library* — what? [Just... call the normal method](http://play.integer32.com/?gist=fe12ab6a13ba9e6ec722d430bb1bc20a&version=stable). – Shepmaster Nov 19 '16 at 00:46
  • Ah, sorry, I forgot that `Vec` is overloaded to call through to produce the slice `Iter`; but in order to declare the Iter wrapper to contain this, it still requires we know that `Vec::iter()` produces and in what module it can be found. – GordonBGood Nov 19 '16 at 00:52
  • @GordonBGood *requires we know that `Vec::iter()` produces* — which is available in the API docs and from the compiler. It's also part of the public API, so it can't really change. – Shepmaster Nov 19 '16 at 00:56