5 ms·
You can 100% have multiple mutable references to the same variable in the same scope, without the first one getting dropped. For example: fn main() {
by legerdemain 3y ago
You can 100% have multiple mutable references to the same variable in the same scope, without the first one getting dropped. For example:
fn main() {
let mut s = String::from("s");
let mut1: &mut _ = &mut s;
let mut2: &mut _ = &mut *mut1;
*mut2 = String::from("t");
println!("using mut2: {mut2}");
println!("using mut1: {mut1}");
}
Some people use a mental model of reference lifetimes that allows "discontinuous lifetimes" where the valid region has holes. I don't think that's how the compiler models reborrowing, and even under that model, `mut1` is created before `mut2` and gets dropped after `mut2`.
- jayshua 3y agoI read this as mut1 and mut2 both being downgraded to shared references because they aren't used to mutate anymore. I'd imagine that's not what's actually happening though?
- vlovich123 3y agoNo - that they are both &mut _ indicates that a mutable reference is being acquired regardless of whether or not they’re used for any mutation. Possibly the compiler could automatically lower to a shared reference if it detects no mutation access locally but there may be design reasons why that’s impossible (+ you can’t have a mutable and a shared reference simultaneously anyway so downgrading to shared would still be disallowed)
- saghm 3y ago> and even under that model, `mut1` is created before `mut2` and gets dropped after `mut2` I think what you're calling "holes" are what I'd just consider to be "lazy" borrowing, where the reference doesn't really matter until the first time it's dereferenced. The only use of mut1 that happens before the end of mut2's lifetime is to assign a reference mut2 to a value that is never used. In other words, mut1 is never actually dereferenced while mut2 exists. I don't consider `&mut *` as dereferencing because it's just syntax for saying "make a new mutable reference to the same thing", which is necessary because mutable references don't implement Copy or Clone. If you instead used `let mut2 = &mut s;`, it would be abundantly clear; they don't really overlap in any way that matters because nothing would change if mut1 didn't exist at all until after mut2 no longer needed to.
- legerdemain 3y agoI don't understand what you're saying. Are you saying that "lazy borrowing" is how the compiler works, or that it's just how you mentally model Rust for your own convenience? There is no "lazy borrowing" in the Rust compiler, as far as I know. You can create a mutable reference `mut1`, mutate through it, create a new mutable reference `mut2`, mutate through it, then mutate through `mut1` again. Here is an example to illustrate: fn main() { let mut s = String::from("1"); // create value let mut1: &mut _ = &mut s; // create a ref mut *mut1 = String::from("2"); // write through mut1 println!("using mut1: {mut1}"); // print mut1 let mut2: &mut _ = &mut *mut1; // create a second ref mut *mut2 = String::from("3"); // write through mut2 println!("using mut2: {mut2}"); // print mut2 *mut1 = String::from("4"); // write through mut1 again println!("using mut1: {mut1}"); // print mut1 } Lifetime analysis can infer shorter lifetimes for unused refs. But creating refs in the wrong order matters even if you never use them, and not all orders are valid Rust programs. Here's a very small example that doesn't compile: fn main() { let mut s = String::from("1"); let mut1: &mut _ = &mut s; let mut2: &mut _ = &mut s; // never used! println!("Using mut1: {mut1}"); }
- saghm 3y agoThe "lazy borrowing" was just a personal mental model, not any claim about how things are implemented under the hood. That said, I think all your examples are hinging on very specific behavior from the `Deref` and `DerefMut` traits that might not apply generally. Looking at the generating MIR output[0] from your second example changed to use the deref syntax from the first one, it seems like the value of `mut1` is silently getting downgraded to `&[&str]` under the hood, presumably due to the fact that the rules for `Deref`[1] and `DerefMut`[1] allow the compiler to liberally substitute dereferences with calls to those trait methods, which crucially allows the compiler to change the result of dereferencing `&mut` to return a shared reference. This is actually almost exactly what was being suggested in the sibling comment here: https://news.ycombinator.com/item?id=39557697 https://news.ycombinator.com/item?id=39557697. [0]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=30c1c3fda19450fee4519eb49720e2a2 https://play.rust-lang.org/?version=nightly&mode=debug&editi... [1]: https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coercion https://doc.rust-lang.org/std/ops/trait.Deref.html#deref-coe... [2]: https://doc.rust-lang.org/std/ops/trait.DerefMut.html#mutable-deref-coercion https://doc.rust-lang.org/std/ops/trait.DerefMut.html#mutabl... (edited shortly after posting to rephrase without needing to use deref operator in the in-line snippets because it messed up the italics of all of the text after like this)
- Hugsun 3y agoVery interesting, thanks for showing me. Messing with the code, it fails if you access mut1 before the mutation of mut2 because of the mutable borrow. I did know but forget that all variables are dropped at the end of a scope, not earlier. Borrows can however last for shorter times and as this example illustrates. They can be borrowed multiple times, as long as they are returned before the parent uses their borrow.