Another mutably borrowed value in loops problem

I have code like this

struct Foo<'a> {
    _marker: PhantomData<&'a ()>,
}

impl<'a> Foo<'a> {
    fn foo(&mut self, bar: &'a Bar) {}
}

struct Bar {}

And when I call foo in loops, it works:

    let mut foo = Foo { _marker: PhantomData::default() };
    let bars: Vec<Bar> = vec![];
    for bar in &bars {
        foo.foo(bar);
    }

And if I add a function to Bar:

impl Bar {
    fn bar<'a>(&'a self, foo: &'a mut Foo<'a>) {
        foo.foo(self);
    }
}

Then call like this:

    let mut foo = Foo { _marker: PhantomData::default() };
    let bars: Vec<Bar> = vec![];
    for bar in &bars {
        bar.bar(&mut foo);
    }

The compiler will report an error:

error[E0499]: cannot borrow `foo` as mutable more than once at a time
  --> src/main.rs:23:17
   |
23 |         bar.bar(&mut foo);
   |                 ^^^^^^^^ `foo` was mutably borrowed here in the previous iteration of the loop

If I insist on using function bar, Is there any safe/unsafe way to handle this problem?

That's a red flag. By creating a reference of this type, you're essentially saying "this reference will exclusively lock Foo for as long as the reference inside Foo is alive, that is, for the whole Foo's existence". What's the real requirement for the lifetimes connections? Do the Foo::foo method have to accept &'a Bar and not some &'short Bar, for example?

4 Likes

Foo::foo is an API from another crate in this case, which I cannot change. The function bar is a trait impl in my case, to wrap the use of foo.

Why I do this (wrap the use of foo in Bar) is that the foo in the real case is very complicated, but in my case, it is only related to Bar, so the wrapping will make the code clear.

Here's a more real case, maybe illustrate whether what I'm doing is a XY problem:

struct Foo<'a> {
    _marker: PhantomData<&'a ()>,
}

impl<'a> Foo<'a> {
    fn add_bar(&mut self, bar: &'a Bar, a: usize, b: usize, c: usize) {}
    fn add_baz(&mut self, baz: &'a Baz, a: usize, b: usize) {}
}

struct Bar {}
struct Baz {}

// code above is the API

trait Bax {
    fn add<'a>(&'a self, foo: &'a mut Foo<'a>);
}

impl Bax for Bar {
    fn add<'a>(&'a self, foo: &'a mut Foo<'a>) {
        foo.add_bar(self, 0, 0, 0);
    }
}

impl Bax for Baz {
    fn add<'a>(&'a self, foo: &'a mut Foo<'a>) {
        foo.add_baz(self, 1, 1);
    }
}

fn main() {
    let mut foo = Foo { _marker: PhantomData::default() };
    let bars: Vec<Bar> = vec![];
    // for bar in &bars {
    //     foo.add_bar(bar, 0, 0, 0);
    // }
    for bar in &bars {
        bar.add(&mut foo);
    }
}

The key reason here is that I want to abstract function add_bar and add_baz into a more generic function, and lucky in this case, parameters like a, b, c are fixed values.

Given this type of usage, it seems that the error is that the &'a mut Foo<'a> should be &mut Foo<'a>. The reason it needs to change was already mentioned. The reason this is the right change is because foo is only used temporarily within the function, so the mutable reference does not need to live any longer — does not need to match any named lifetime.

4 Likes

That's it, thanks a lot.
BTW, is there any good resource to practice using lifetimes? I'm always getting troubles using it.

Lifetimes are very logical, and they don't follow excessively complicated rules. So if you have trouble with them (i.e. your code doesn't compile due to lifetime errors), then it's probably not because e.g. you don't know 50 of the 100 rules.

On the contrary: you probably just aren't aware of every place your data is borrowed from or perhaps of the exact time where some value is created. It would therefore be helpful to visualize the various regions involved in your code, e.g. by drawing some paper-and-pencil diagrams, or by annotating your lifetimes as named/labelled blocks in your code. Making constraints explicit can be of substantial help if you have trouble keeping every minute detail in your head.

1 Like

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.