Summary: We should allow multiple mutable references to the same value within a function, and those references should not be considered borrows except for where the references are used.
Rust currently allows reborrowing in some limited cases. This is a step towards achieving this feature. Here is a minimal example that compiles:
let mut x = vec![1, 2, 3];
let y = &mut x;
let z: &mut Vec<i32> = y;
z.push(4);
y.push(5);
(copied from better documentation of reborrowing · Issue #788 · rust-lang/reference · GitHub)
We call this a "reborrow", meaning that y
's borrow of x
is relinquished to z
and then reclaimed when z
is done. But the semantics I am proposing here is a different model - y
does not borrow x
for its scope - it only borrows x
when it is used. So z
is free to borrow x
in between usages of y
.
Here is an example that does not work with reborrowing, but would compile with the proposed feature:
let mut x = 0;
let mx = &mut x; // here we take a reference, but it is not a borrow (yet)
let rx = &x; // here we take a reference, but it is not a borrow (yet)
dbg!(rx); // x is borrowed here
dbg!(mx); // x is borrowed here
The most motivating example is closures. Closures would be way more useful with this feature:
let mut acc: Vec<Result<Item, Error>> = Vec::new();
// `add` contains a mutable reference to `acc`,
// but it does not borrow `acc` until it is used or invoked
let mut add = |v| acc.push(Ok(v));
if unlucky {
acc.push(Err(e)); // `acc` is borrowed here (this doesn't compile today)
}
if cond1 {
add(x1); // `acc` is borrowed here
add(x2); // `acc` is borrowed here
}
(copied from Local closures borrowing ergonomics with edits)
Things get complicated when a reference can borrow one of multiple things. But I believe this is still possible.
let mut x = if condition { &mut a } else { &mut b };
if another_condition { x = &mut c }
dbg!(x); // `a` or `b` or `c` may be borrowed here
Okay now commence shooting holes in this.
Edit: Derived values
Given x
, any values "derived" from x
must be dropped before x
may be mutably borrowed (through a &mut) again. A derived value is anything whose type contains a lifetime that is inherited from x
or some projection of x
(a reference, field, index, etc.). Perhaps these may be called "projected references".
let mut x: [Option<u32>; 1] = [Some(1)];
let mx = &mut x;
// this is okay because `mx` is not borrowing `x` yet,
// and `mx` references `x` itself, not an inner or derived value;
// there are no values derived from `x` in scope
x = [Some(2)];
// `inner` is "derived" from `x` since its lifetime is inherited from a reference to `x`
// so it must be dropped before a mutable borrow of `x` may occur
let inner: &u32 = mx[0].as_ref().unwrap();
dbg!(inner);
drop(inner);
// this is okay because `inner` is dropped
// (perhaps `drop()` is not required since `inner` is not a `Drop` type,
// but `inner` is certainly not usable after `x` is mutably borrowed below)
*mx = [None];