3

I have a piece of sample code here

let mut s1: String = String :: from("Hello 2");
let s2 = &mut s1;
println!("This is s2: {}", s2);
s1  = s1 + "World";
//println!("This is s1 : {} and s2: {}",s1,s2);

Please note that I have commented out the println!() statement. From my understanding of rust ownership and borrowing rules, rust does not permit more than one mutable reference to a mutable object at a time. Since s2 has borrowed mutably from s1, I understand that the compiler must throw an error. Yet this is what I get:

warning: value assigned to `s1` is never read
 --> src/main.rs:5:5
  |
5 |     s1  = s1 + "World";
  |     ^^
  |
  = note: #[warn(unused_assignments)] on by default
  = help: maybe it is overwritten before being read?

    Finished dev [unoptimized + debuginfo] target(s) in 0.55s

When I uncomment the println line, I immediately get three errors. The error messages are as follows:

error[E0506]: cannot assign to `s1` because it is borrowed
 --> src/main.rs:5:5
  |
3 |     let s2 = &mut s1;
  |              ------- borrow of `s1` occurs here
4 |     println!("This is s2: {}", s2);
5 |     s1  = s1 + "World";
  |     ^^ assignment to borrowed `s1` occurs here
6 |     println!("This is s1 : {}, s2 : {}", s1, s2);
  |                                              -- borrow later used here

error[E0505]: cannot move out of `s1` because it is borrowed
 --> src/main.rs:5:11
  |
3 |     let s2 = &mut s1;
  |              ------- borrow of `s1` occurs here
4 |     println!("This is s2: {}", s2);
5 |     s1  = s1 + "World";
  |           ^^ move out of `s1` occurs here
6 |     println!("This is s1 : {}, s2 : {}", s1, s2);
  |                                              -- borrow later used here

error[E0502]: cannot borrow `s1` as immutable because it is also borrowed as mutable
 --> src/main.rs:6:42
  |
3 |     let s2 = &mut s1;
  |              ------- mutable borrow occurs here
...
6 |     println!("This is s1 : {}, s2 : {}", s1, s2);
  |                                          ^^  -- mutable borrow later used here
  |                                          |
  |                                          immutable borrow occurs here

error: aborting due to 3 previous errors

What is even more confusing is that no error is thrown and cargo builds without a hitch if I only println! s1, instead of both s1 and s2 at the end. The error remains if I only print s2.

From my understanding of rust, shouldn't there be an error at s1 = s1 + "World" regardless of what I print after this assignment, since s2 has already mutably borrowed s1?

  • I believe this is either the proposed duplicate or the compiler just throwing/optimizing that `s1 = s1 + "World";` away. I mean, why would it even care about that line if you never read from `s1` afterwards. There is no need to mutate it if it does not affect any code that follows it. – Fynn Becker Sep 03 '19 at 13:43
  • 1
    @FynnBecker The all compiler checks (like type checking, borrow checking, ..) work on the unoptimized version. But yes, the fact that "something is not used later" is a big part why NLL work. – Lukas Kalbertodt Sep 03 '19 at 13:47
  • Shreyas: if the linked duplicate does not answer your question in your opinion, please let us know. But yes, the answer to your question is basically "NLL is smart about stuff". In your code, since `s2` isn't used after the first print, the compiler can treat it as dropped, thus `s1` is not considered borrowed anymore. – Lukas Kalbertodt Sep 03 '19 at 13:49
  • 1
    Lukas: It seems to. I guess NLL is the answer. But it also appears to break program semantics of mutable borrows at a glance. It becomes much harder to track lifetimes of a borrow in large functions if they are not defined by scope. All it takes is for one line of code to be commented in or out, to change the lifetime of a borrow. Would it be a good idea to raise an issue on rust's repo to add a warning for modifying a mutable reference when another mutable borrow exists as in my example? – Shreyas Shankar Srinivas Sep 03 '19 at 14:00
  • 1
    I don't think it's correct to say "it breaks semantics". Rust will always reject some safe programs, as rejecting only exactly unsafe programs is basically halting problem yada yada. NLL tries to reduce the number of safe programs Rust rejects. I am pretty sure some Rust experts thought really hard about why NLL is sound (i.e. does not allow unsafe programs to compile). Maybe I also misunderstood what you meant by "break semantics". But I guess this is not a discussion for SO comments. [The chat](https://chat.stackoverflow.com/rooms/62927/rust) or the users forum might be more appropriate. – Lukas Kalbertodt Sep 03 '19 at 14:22
  • I am new here, so I don't have the reputation for joining the chat. Formally if one re-defines the borrow rules using lifetimes instead of scope and explains lifetimes properly (in the rust-book chapter 4), then there is no problem, at least formally. But this hasn't actually been mentioned in that chapter in the book. This is likely to create borrow checker issues that take a long time to trace when maintaining and modifying code with large lexical scopes. It breaks semantics in the user's head, especially if they are migrating from the 2015 edition and read the book as it is. – Shreyas Shankar Srinivas Sep 03 '19 at 14:40
  • The book doesn't (as far as I am aware) discuss lifetimes in terms of *scope* at all. Where lifetimes are introduced in the book, they are introduced in terms of how they *disallow unsound code* (probably because the precise semantics are quite complex). The code in your question is not unsound, so there's no reason to assume (based on what's in the book) that it should be rejected. If there is (still) something in the book that led you to believe that lifetimes are based on scope, you should file an issue to improve it, since that's not true of Rust today. – trent Sep 03 '19 at 17:42
  • True. But in chapter 4, the book essentially conflates scope with lifetime. In chapter 4-01: "the memory is automatically returned once the variable that owns it goes out of scope". In chapter 4-02: "mutable references have one big restriction: you can have only one mutable reference to a particular piece of data in a particular *scope*". Further in a code listing below, it is suggested that a block can be used to limit the *scope*. There is no indication that there is any difference between the lifetime and block scope of a borrow anywhere. A word on lifetimes and NLL is needed in 4-02. – Shreyas Shankar Srinivas Sep 03 '19 at 18:56
  • 1
    I have raised an issue on the repository for the book : https://github.com/rust-lang/book/issues/2077 – Shreyas Shankar Srinivas Sep 04 '19 at 12:32

0 Answers0