Search code examples
rustreferencelifetime

Why does conditionally returning an immutable reference in a loop inhibit creating a mutable borrowing in the loop after that reference dropped?


fn foo(var: &mut String) -> &String {
    let mut counter = 1;
    let result = loop {
        {
            let var_ref = &*var;
            counter += 1;
            if counter > 10 {
                break var_ref;
            }
        }
        *var += "a";
    };
    result
}

In the code above, the loop makes an immutable reference of variable 'var' passed by parameter, and then adds "a" to 'var' after the reference dropped when counter is not bigger then 10. When counter is bigger than 10, the loop ends before the mutable borrowing occurs. So, when the mutable borrowing occurs, var_ref doesn't exist, and when var_ref was returned from loop, the mutable borrowing doesn't occur.

But it makes an error:

error[E0502]: cannot borrow `*var` as mutable because it is also borrowed as immutable
  --> src\main.rs:13:9
   |
3  | fn foo(var: &mut String) -> &String {
   |              - let's call the lifetime of this reference `'1`
...
7  |             let var_ref = &*var;
   |                           ----- immutable borrow occurs here
...
13 |         *var += "a";
   |         ^^^^^^^^^^^ mutable borrow occurs here
14 |     };
15 |     result
   |     ------ returning this value requires that `*var` is borrowed for `'1`
  • If I remove the last line of the function 'result', the error disappears.

Solution

  • This is a known limitation of the borrow checker. It will be fixed once the polonius borrow checker gets released. There are several workarounds; usually they involve the polonius_the_crab crate.

    In your case, however, it's easier. Simply prevent the reference from existing outside of the if clause:

    fn foo(var: &mut String) -> &String {
        let mut counter = 1;
        let result = loop {
            counter += 1;
            if counter > 10 {
                break var;
            }
    
            *var += "a";
        };
        result
    }
    

    Although your code has a lot of inefficiencies in it; it can be simply rewritten as such:

    fn foo(var: &mut String) -> &String {
        for _ in 1..10 {
            *var += "a";
        }
        var
    }
    

    Or even shorter:

    fn foo(var: &mut String) -> &String {
        var.extend(std::iter::repeat('a').take(9));
        var
    }