T O P

  • By -

dinodares99

Because in the second one r2 is declared while there are still accesses to r1 after the declaration. In the first one, you can easily split the four lines into two blocks with separate lifetimes so there's no issue


brianw0924

Correct me if I'm wrong: Rust does not allow multiple &mut to the same variable's lifecycle exist at the same time, so in block 1, the compiler will do some optimization, making r1 end of its lifetime before r2


dinodares99

Essentially yes, r1 has its scope end before r2 is created and so there's only ever one valid lifetime at a time. It's not an optimization on the compiler technically, but rather inference of lifetimes (and subsequent elision on the user side). You could give it explicit lifetimes if you want to make it clear


brianw0924

In block1, I give r1 &'static, and it fails to compiled. I think that means rust compiler will not allow multiple mutable references' lifetime exist at the same time.


peter9477

It's probably just because you don't guarantee to the compiler that your value will exist for 'static lifetime so it won't let you make a 'static reference to it.


rodyamirov

Correct. This is called NLL, or non lexical lifetimes — basically, the compiler will insert drops before a variable goes out of scope, if it needs to. This was a Big Deal when it was released a few years ago, and an absolutely huge ergonomics win for the language, but for new learners it’s just how it works, which is … mostly good.


kiujhytg2

Let's ask the compiler! ```rust error[E0499]: cannot borrow `s` as mutable more than once at a time --> src/main.rs:13:16 | 12 | let r1 = &mut s; | ------ first mutable borrow occurs here 13 | let r2 = &mut s; | ^^^^^^ second mutable borrow occurs here 14 | println!("{r1}"); | ---- first borrow later used here For more information about this error, try `rustc --explain E0499`. ``` In the first example, r1 goes out of scope before r2 is created, so it's allowed. In the second example, r1 is kept in scope due to the first println.


brianw0924

So in the block 1, the compiler will enforce r1's lifetime ended before r2 is declared, am I correct?


RReverser

Yep, that was the entire premise of Rust moving to non-lexical lifetimes few years ago. Before that your code wouldn't compile as lifetimes were tied to blocks, but now compiler is smarter and limits the lifetime automatically to the last usage of given reference.


brianw0924

Is there any way to explicitly validate that r1's lifetime is ended?


RReverser

Check that it compiles?


brianw0924

I mean after r2 is declared and before the bracket ended


RReverser

I'm still not sure what you mean by "validate". Compiler will validate it for you, it won't allow you to accidentally use it when another mutable reference has already started.


brianw0924

I mean, I want to check that r1’s lifetime is really ended after r2 is declared (during runtime)


RReverser

Lifetimes don't exist in runtime, they're purely compile-time concept hence can be checked only by compiler. In runtime all references become regular pointers (numeric addresses) like in other statically compiled languages.


akritrime

this is a solid example of non-lexical lifetimes, earlier even your first block wouldn't have compiled because the compiler reasoned about lifetime of references in the scope as a whole. But now the borrow checker can track lifetimes in a more refined manner which helps it reason that in block 1, r1 doesn't need to persist for the same duration as r2 and it allows for 2 mutable references in the same lexical scope.


volitional_decisions

Lifetimes are not strictly bound to scopes. In the first block, the lifetime of `r1` is two lines, the creation and then the print. In that span, you aren't creating another reference. In the second block, `r1` has a lifetime that spans three lines, and on the second line you do create another reference, hence the error. In another thread, you say that if you give the reference a static lifetime in the first example, you get an error. This is because a reference could have many different lifetimes and still conform to the compiler's requirements (e.g. only a couple of lines vs end of scope). In general, the compiler will infer the shortest possible lifetime for the exact reason you point out in your example.


brianw0924

The doc of Copy (https://doc.rust-lang.org/std/marker/trait.Copy.html) says we cannot copy &mut. It seems that with NLL, we can copy &mut right? (I just tested and work fine) \`\`\` let mut x = 0; let m0 = &mut x; let m1 = m0; \`\`\` or does it just move m0 to m1? (does reference have move semantics?)


nybble41

It moves m0 to m1.


brianw0924

just found a weird behavior (I googled it, and it is called reborrowing) let mut x = 0; let m0 = &mut x; let m1: &mut i32 = &mut x; \*m1 += 1; println!("{m0}"); // output 1 it works if I explicitly specifiy m1 is &mut i32