Returning iterator over copied values from trait function

Viewed 55

I require for a trait to have a function that returns an iterator over some value-type. A struct implementing this trait may simply store a (read-only) collection of values we want to iterate over.

I attempted a contrived example below:

use std::iter::Copied;
use std::collections::{hash_set, HashSet};

trait MyTrait<I: IntoIterator<Item = usize>> {
    fn iter(&self) -> I;
}

struct MyStruct{
    set: HashSet<usize>,
}

type Iter<'a> = Copied<hash_set::Iter<'a, usize>>;

impl MyTrait<Iter<'_>> for MyStruct {
    fn iter(&self) -> Iter<'_> {
        self.set.iter().copied()
    }
}

However, the above code yields the following compiler errors:

error: `impl` item signature doesn't match `trait` item signature
  --> src/lib.rs:15:5
   |
5  |     fn iter(&self) -> I;
   |     -------------------- expected `fn(&MyStruct) -> Copied<std::collections::hash_set::Iter<'_, usize>>`
...
15 |     fn iter(&self) -> Iter<'_> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(&MyStruct) -> Copied<std::collections::hash_set::Iter<'_, usize>>`
   |
   = note: expected `fn(&MyStruct) -> Copied<std::collections::hash_set::Iter<'_, _>>`
              found `fn(&MyStruct) -> Copied<std::collections::hash_set::Iter<'_, _>>`
help: the lifetime requirements from the `impl` do not correspond to the requirements in the `trait`
  --> src/lib.rs:5:23
   |
5  |     fn iter(&self) -> I;
   |                       ^ consider borrowing this type parameter in the trait

The "expected" and "found" appear identical, so maybe this is an issue with anonymous lifetimes? How can I get this to work?

Link to playground

1 Answers

The problem is that the compiler can't infer the correct lifetimes here, so you need to tell it what they are, like this:

impl<'a> MyTrait<Iter<'a>> for MyStruct {
    fn iter(&'a self) -> Iter<'a> {
        self.set.iter().copied()
    }
}

Unfortunately, you can't do that, because the trait doesn't have a lifetime requirement on self so neither can an implementor. To allow this, you can simply change the trait to this:

trait MyTrait<'a, I: 'a + IntoIterator<Item = usize>> {
    fn iter(&'a self) -> I;
}

Here I: 'a + ... is telling the compiler that no reference inside I may outlive 'a, i.e. self.

Now you can implement MyTrait like this:

impl<'a> MyTrait<'a, Iter<'a>> for MyStruct {
    fn iter(&'a self) -> Iter<'a> {
        self.set.iter().copied()
    }
}

And for a type where the lifetimes really don't matter, because the I doesn't actually hold any references, you can still use lifetime elision:

impl MyTrait<'_, std::vec::IntoIter<usize>> for Vec<usize> {
    fn iter(&self) -> std::vec::IntoIter<usize> {
        self.clone().into_iter()
    }
}
Related