Rust compiler has unintuitive lifetime for returned mutable borrow in loops

20fea46
Opened by Kevin Xiao at 2020-08-30 02:19:53

Pardon if I have an incorrect view of how the borrow/lifetime system works.

Compiler complains about multiple mutable borrows in a loop, if a mutable borrow can be returned from the function.

Consider the following:

use std::collections::VecDeque;
struct Something;
struct Struct1 {
    list: VecDeque<usize>,
    thing: Something,
}
impl Struct1 {
    fn get_mut(&mut self) -> &mut Something {
        for _ in self.list.iter() {
            let a = &mut self.thing;
            if true {
                return a;
            }
        }
        panic!("hi");
    }
}

I get the following compilation

error[E0499]: cannot borrow `self.thing` as mutable more than once at a time
  --> src/main.rs:20:26
   |
20 |             let a = &mut self.thing;
   |                          ^^^^^^^^^^ mutable borrow starts here in previous iteration of loop
...
27 |     }
   |     - mutable borrow ends here
...
error: aborting due to previous error

It is not expected to me that the lifetime of the mutable reference would clash. It seems that the only reason the lifetime of a is considered so long is because it can be returned. However, if it is being returned, I don't see why it should be considered a conflict, as it won't continue with the loop.

I'm not familiar with the details of the compiler, but I would expect, and I would want, the compiler to end the lifetime of the mutable borrow at the end of the loop. Or at the end of containing braces if there are any.

  1. As far as I understand this should be fixed by non-lexical lifetimes; it's akin to problem 3 in the blog post.

    Alex Burka at 2017-10-21 17:10:49

  2. As of rust 1.39, is this problem resolved by nll ?

    Alex.F at 2019-11-12 01:52:27

  3. No, the original code still fails to compile.

    8  |     fn get_mut(&mut self) -> &mut Something {
       |                - let's call the lifetime of this reference `'1`
    9  |         for _ in self.list.iter() {
    10 |             let a = &mut self.thing;
       |                     ^^^^^^^^^^^^^^^ mutable borrow starts here in previous iteration of loop
    11 |             if true {
    12 |                 return a;
       |                        - returning this value requires that `self.thing` is borrowed for `'1`
    

    Alex Burka at 2019-11-13 01:28:46

  4. Looks like this is similar to https://github.com/rust-lang/rust/issues/51132 which suggests it's addressed with polonius. And indeed, building with -Zpolonius (rustc --version is rustc 1.45.0-nightly (7ebd87a7a 2020-05-08)) looks to succeed:

    [19:17:37] # iximeow:~> rustc -Zpolonius --crate-type cdylib test_polonius2.rs 
    warning: struct is never constructed: `Something`
     --> test_polonius2.rs:2:8
      |
    2 | struct Something;
      |        ^^^^^^^^^
      |
      = note: `#[warn(dead_code)]` on by default
    
    warning: struct is never constructed: `Struct1`
     --> test_polonius2.rs:3:8
      |
    3 | struct Struct1 {
      |        ^^^^^^^
    
    warning: associated function is never used: `get_mut`
     --> test_polonius2.rs:8:5
      |
    8 |     fn get_mut(&mut self) -> &mut Something {
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    warning: 3 warnings emitted
    

    iximeow at 2020-08-30 02:19:19