5

I'm trying to create a trait that captures the iter function in slice as well as VecDeque, BTreeMap and HashMap. I'd like the implementer of this trait to be able to specify and implement their own iterator type, but it looks like this iterator type must have a lifetime argument, and that cannot be given as an associated type.

In more detail, here's what I wish was possible in Rust:

trait RefIterable<T>
where for<'a> (T: 'a) => (Self::Iter<'a>: Iterator<Item = &'a T>)
{
  type Iter; // Has kind (lifetime -> type)
  fn refs<'a>(&'a self) -> Self::Iter<'a>
}

If this was possible, the implementation could look like this

impl RefIterable<T> for Vec<T> {
  type Iter<'a> = std::slice::Iter<'a, T>; // This is not valid Rust code.
  fn refs<'a>(&'a self) -> std::slice::Iter<'a, T> {
    self.as_slice().iter()
  }
}

I'm still relatively new to Rust, so I'm asking if there's already a way to do this that I'm not aware of, or if there's a nice workaround for this situation. I'd imagine that this situation is not very rare.

(Using Box<dyn 'a + Iterator<Item = &'a T>> is my current workaround, but that prevents some optimization from happening.)

Edit:

EvilTak's answer is probably the best thing we can do right now. The ability to combine all possible lifetimes together with the condition T: 'a into one unparametrized trait seems to be unsupported by Rust as of today.

Tunococ
  • 195
  • 7

1 Answers1

2

Add the lifetime parameter to the trait instead, which allows you to use it in the associated type Iter's bound:

trait RefIterable<'a> {
    type Item: 'a;
    type Iter: Iterator<Item = &'a Self::Item>; // Has kind (lifetime -> type)
    fn refs(&'a self) -> Self::Iter;
}

The Item: 'a bound is required to let the compiler know that the references (&'a Self::Item) do not outlive the type (Self::Item).

I have modified RefIterable to make it follow Iterator's convention of using an associated type to specify the type of the items that are iterated over for the same reason as the one behind Iterator's usage of an associated type.

Implementations are pretty straightforward:

impl<'a, T: 'a> RefIterable<'a> for Vec<T> {
    type Item = T;
    type Iter = std::slice::Iter<'a, T>;
    
    fn refs(&'a self) -> std::slice::Iter<'a, T> {
        self.as_slice().iter()
    }
}

Playground

EvilTak
  • 7,091
  • 27
  • 36
  • Ah. I have tried this and I knew this was one of my attempts. Then I realized that my original post was missing some more context. It wasn't `RefIterable` that I wanted to make, but I wanted to make a larger trait which contains other functions that don't rely on 'a as well. Let me edit the original post. – Tunococ Jan 21 '21 at 09:12
  • Actually, let me accept this answer for now, then I'll create another question. – Tunococ Jan 21 '21 at 09:13
  • Now that I think more about it, my original question hasn't really been answered. I was asking for a trait, but your answer gives an infinite number of traits. – Tunococ Jan 21 '21 at 09:17
  • @Tunococ what do you mean by an "infinite number of traits"? Unlike generics, there is no monomorphization for lifetime parameters (if that is what you meant by my answer giving an infinite number of traits) -- they are simply a means for specifying lifetimes to the borrow checker and have no impact on the code generated by the compiler. – EvilTak Jan 21 '21 at 09:44
  • It has an impact on usability inside another constraint. But I'm feeling like we're hitting the language limitations here, so your answer might be the best workaround for now. – Tunococ Jan 21 '21 at 09:56