Strange lifetime error

In the simple program:

struct Foo {
    a: u32,
}

impl Foo {
    fn f(&mut self) -> Option<&u32> {
        for _ in 0..2 {
            let x = &mut self.a;
            if *x > 0 {
                return Some(x);
            }
        }
        None
    }
}

fn main() {}

rustc 1.58.1 (db9d1b20b 2022-01-20) complained about a lifetime error:

error[E0499]: cannot borrow `self.a` as mutable more than once at a time
  --> test2.rs:8:21
   |
6  |     fn f(&mut self) -> Option<&u32> {
   |          - let's call the lifetime of this reference `'1`
7  |         for _ in 0..2 {
8  |             let x = &mut self.a;
   |                     ^^^^^^^^^^^ `self.a` was mutably borrowed here in the previous iteration of the loop
9  |             if *x > 0 {
10 |                 return Some(x);
   |                        ------- returning this value requires that `self.a` is borrowed for `'1`

I'm puzzled by the error because the borrow &mut self.a ends at the end of each iteration of the for loop. If I change the program slightly into the following, the error goes away.

struct Foo {
    a: u32,
}

impl Foo {
    fn f(&mut self) -> Option<&u32> {
        for _ in 0..2 {
            let x = &mut self.a;
            if *x > 0 {
                return Some(&mut self.a);
            }
        }
        None
    }
}

fn main() {}

This is a limitation of the current borrow checker algorithm. When you have a borrow that is conditionally returnedx in this program — it is considered to have a lifetime that extends from the point it was created to the end of the function's execution. The borrow checker does not understand that the if makes it so that either the function returns immediately or the borrow is dropped; instead, it analyzes the program as if you were collecting the borrow from every loop iteration and then returning them at the end (which would be a conflict because you then have multiple overlapping &mut self.a).

The general solution is as in your second program: keep strictly separate borrows for

  • what you do within the loop before you have decided whether to continue or return, and
  • what you return.

(Here, it's cheap, but this gets more annoying when you're doing a redundant lookup in a HashMap or such.)

2 Likes

This is the issue ("NLL problem case #3") in case you want to follow it.

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.