Help with the borrow checker on trait's lifetime parameters

Hi, I'm not sure if this has been asked. I have been playing with this for a while and can't figure out why:
Here is a simplified example showing what I'm trying to do. Basically, I'm creating a trait that provides iter_mut on a wrapper type.

trait Foo<'a, T: 'a> {
    type Iter: Iterator<Item = &'a mut T>;

    fn iter_mut(&'a mut self) -> Self::Iter;
}

struct Bar<T> { 
    v: Vec<T>,
}

impl<'a, T: 'a> Foo<'a, T> for Bar<T> {
    type Iter = std::slice::IterMut<'a, T>;

    fn iter_mut(&'a mut self) -> Self::Iter {
        self.v.iter_mut()
    }
}

fn test<'a, IN: Foo<'a, i32>>(t: &'a mut IN) {
   t.iter_mut().for_each(|v| *v += 1);
   t.iter_mut().for_each(|v| *v += 2);
}

fn main() {
   let mut b = Bar { v: vec![0_i32; 10] };
   b.iter_mut().for_each(|v| *v += 1);   
   b.iter_mut().for_each(|v| *v += 2);
   println!("{:?}", b.v)
}

The compiler complains the test function mutably borrows twice. But the same code in the main function has no problem. Here is the playground code to show the compile error.

Can anyone kindly provide some clue how I can fix this? Thanks!

Your test function should require that IN has an iter_mut method for any lifetime, not just for the lifetime 'a. You do that with this syntax:

fn test<'a, IN: for<'b> Foo<'b, i32>>(t: &'a mut IN) {
   t.iter_mut().for_each(|v| *v += 1);
   
   t.iter_mut().for_each(|v| *v += 2);
}

Note that there's already the IntoIterator trait, which is often implemented on mutable references to give a generic iter_mut.

2 Likes

This is super helpful! It indeed works. I didn't realize I need HRTB here. Also thanks for the suggestion of IntoIterator trait

@alice Would you mind to explain a bit why we need HRTB here? Especially, I still can't fully understand why in my original code the two iter_mut() calls in the main function work fine but fail in the test function.

Thanks again!

In your example, all we know is that if we borrow t for the lifetime 'a, we can call iter_mut on it. However as 'a is a generic parameter, it must strictly contain the entire test function. However that means that both iter_mut calls borrow t for at least the entire test function. That's two overlapping mutable borrows, hence the error.

The HRTB works because we are suddenly able to call iter_mut while only borrowing t for some other shorter lifetime, allowing each iter_mut call to choose two small and disjoint lifetimes.

1 Like

I see. That makes sense. Thanks a lot for the explanation!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.