2

From Rust documentations:

mutable references have one big restriction: you can have only one mutable reference to a particular piece of data in a particular scope.

The example in the docs does support this statement:

let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);

But this is a bit misleading to me. When print statement in the last line is removed the program compiles and runs fine (although with a couple of warnings). Later in the docs they say this is how rust deals with data races. I haven't reached the point where they explain threads and parallel programming but I guess it's still worthwhile to ask how are we preventing the data race in the following case:

fn main() {
    let mut name: String = String::from("Devashish");

    // assume that fun1 was called from a new thread
    fun1(&mut name);

    // fun1 and fun2 are being executed in parallel at this point
    fun2(&mut name);
}

fn fun1(re: &mut String) {
    println!("From fun1: {}", re);
}

fn fun2(re: &mut String) {
    println!("From fun2: {}", re);
}

How are we preventing data race in the case above?

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
  • Voting to reopen as the real answer isn't NLL but anonymous references (I checked that OP's code works with compilers dating from before NLL). There might be another dupe target, though. – Denys Séguret Jul 16 '19 at 11:34
  • @Denys I don't agree: the fact the references are anonymous is made irrelevant by the fact that even if they *weren't*, the code in the question is fundamentally correct and allowed. That older versions of the compiler *used* to reject similar code but treated temporaries differently, doesn't mean anything particularly useful about the language today. – trent Jul 16 '19 at 16:48
  • [Another relevant question](https://stackoverflow.com/q/53954053/3650362) – trent Jul 16 '19 at 16:50
  • @trentcl You don't get it. The older compiler accepted the exact code of OP just like the new ones. It's just not a matter of NLL. NLL solved different cases and it's 100% wrong to say NLL makes this work. The real answer is in a part of the specification which didn't change. – Denys Séguret Jul 16 '19 at 19:12

1 Answers1

2

In this cases, the references are anonymous. The lifetime of the &mut name reference in your call to fun1 is the enclosing statement, that is fun1(&mut name);.

So the lifetime of the first mutable reference ends before the start of the second one.

If you had explicitly declared the references, the order would have determined whether there's a conflict (thanks to NLL).

This isn't valid:

let r1 = &mut name;
let r2 = &mut name;
fun1(r1);
fun2(r2);

This is valid (with a recent enough version of rustc):

let r1 = &mut name;
fun1(r1);
let r2 = &mut name;
fun2(r2);

because the borrow checker can determine that r1 isn't used anymore when r2 is declared.

And as you might discover if you try to put a new thread behind fun1: no, you can't pass the mutable borrow to another thread from inside fun1. As soon as fun1 ends, the mutable borrow is released, it's syntactically guaranteed.

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • 1
    **Not** being lexical doesn't tell you much about how it actually works. It's better to say that lifetimes _used_ to be based purely on lexical scopes, but now they are more flexible so that more valid programs are accepted. Future improvements to lifetime flexibility will also be non-lexical. – Peter Hall Jul 16 '19 at 11:14