At any given time, you can have either one mutable reference or any number of immutable references.
So why is this flagged for error
fn first_word(s: &String) -> &str {
"Hello"
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!
println!("the first word is: {}", word);
}
with the message error[E0502]: cannot borrow ``s`` as mutable because it is also borrowed as immutable
while this
fn first_word(s: &String) -> String {
String::from("Hello")
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!
println!("the first word is: {}", word);
}
compiles without an error. In both the cases in the main() we do an immutable borrow when invoking first_word(). and then a mutable borrow while doing s.clear()
Why doesn't the second version throw the cannot borrow as mutable error?
Hi, I'm not an expert but I think that's what is happening:
The first code doesn't work because by default when the compiler sees a reference in and a reference out it will do this but what you really want is that.
And the second code simply doesn't have this issue.
In the second version, you really don't use the borrow after the finish of the function call (because return value don't reference it). Thanks to non-lexical lifetimes, the borrow is essentially immediately dropped and doesn't interfere with further mutable borrowing.
In the first case, at the same time, return value is (according to the function signature) borrowing from the input. While it is alive, it holds the lock and doesn't allow for mutable borrow to appear.
As @leudz said, this had to do with lifetime elision. If you don't mark lifetimes, thrn Rust will try to infer the lifetimes from the signature alone, and sometimes doesn't do what you want. Importantly, Rust doesn't look at the function body to do lifetime analyses.
It depends, if you assign it, used or not, it exists until the end of scope.
fn main() {
let mut s = String::from("hello world");
let letter = s.get(0..1).unwrap();
// this is dropped on the spot
s.get(0..1).unwrap();
s.clear();
// letter dropped here NLL or not
}
You are right, "dropped" is not the best word.
I would say that temporary borrows end at the end of a statement;
So, although letter is freed at the end of a scope (after s.clear() for instance), thanks to NLL, since there is no drop glue, the borrow can end before s.clear(), thus compiling, whereas without NLL the borrow ends when the reference is dropped, after s.clear(), thus not compiling.