Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mutable borrow in a loop

I have the following code:

struct Baz {
    x: usize,
    y: usize,
}

struct Bar {
    baz: Baz,
}

impl Bar {
    fn get_baz_mut(&mut self) -> &mut Baz {
        &mut self.baz
    }
}

struct Foo {
    bar: Bar,
}

impl Foo {
    fn foo(&mut self) -> Option<&mut Baz> {
        for i in 0..4 {
            let baz = self.bar.get_baz_mut();
            if baz.x == 0 {
                return Some(baz);
            }
        }
        None
    }
}

Rust Playground

It fails to compile with:

error[E0499]: cannot borrow `self.bar` as mutable more than once at a time
  --> src/main.rs:23:23
   |
23 |             let baz = self.bar.get_baz_mut();
   |                       ^^^^^^^^ mutable borrow starts here in previous iteration of loop
...
29 |     }
   |     - mutable borrow ends here

However, if I return Some(baz.x) from Foo::foo (and change the return type to Option<usize>), the code compiles. This makes me believe the problem is not with the loop even though the compiler seems to indicate so. More specifically, I believe the local mutable reference baz would go out of scope at the next iteration of the loop, causing this to be a non-problem. What is the lifetime problem with the above code?

The following questions are similar:

  • Mutable borrow in loop
  • Linking the lifetimes of self and a reference in method
  • Cannot borrow as mutable more than once at a time in one code - but can in another very similar

However, they deal with explicitly declared lifetimes (and specifically these explicit lifetimes are part of the answer). My code omits these lifetimes so removing them is a non-solution.

like image 315
user1413793 Avatar asked May 01 '18 05:05

user1413793


1 Answers

It does not work because returning a borrowed value extends the borrow to the end of the function.

See here for some useful details.

This works with non-lexical lifetimes with the 1.27 nightly version:

#![feature(nll)]

struct Baz {
    x: usize,
    y: usize,
}

// ...

The non-lexical lifetimes RFC explains the actual working of lifetimes:

Problems arise however when you have a reference that spans multiple statements. In that case, the compiler requires the lifetime to be the innermost expression (which is often a block) that encloses both statements, and that is typically much bigger than is really necessary or desired

rustc nightly 1.28

As pointed out by @pnkfelix, the non-lexical lifetimes implementation starting from nightly 1.28 no longer compiles the above code.

There is however a long-term plan to (re)-enable a more powerful NLL analysis.

like image 158
attdona Avatar answered Sep 21 '22 16:09

attdona