Rust: second mutable borrow compiling and running. Why? [duplicate] - rust

This question already has an answer here:
What are non-lexical lifetimes?
(1 answer)
Closed last year.
Folks,
I am writing some code to learn Rust.
I read about the rule that states you can have at most ONE mutable borrow for a variable going on simultaneously in the scope of your code.
Then, while I was writing some reference to myself, I stumbled on this:
fn main() {
let mut a = "Is this string immutable?".to_string();
println!("a: {}\n", a);
let b = &mut a;
b.push_str(" No, it's not.");
let c = &mut a;
// b.push_str(" Could we append more stuff here?");
println!("c: {}",c);
}
The weird situation is: this code works as is, even if I declared two mutable borrows.
BUT... If I comment out the second push_str() call, the compiler would start to complain about the second mutable borrow in the c variable declaration.
What am I missing here? Why is it running?
Thanks in advance.

The rule is that you cant have to mutable borrows which are active at the same time.
In your case this is not the case.
The borrow b just has to be alive until the line
b.push_str(" No, it's not.");.
After this you are free to borrow again, because b is never used again.
When outcommenting you extend the lifetime of b after your second borrow and you get an error.
The compiler is not always able to recognize this in more complicated situations, so it might fail to compile even if at no point there could be 2 mutable borrows.

Related

Multiple borrows in a method that uses another method of the same struct as an argument: why can't the compiler figure it out? [duplicate]

Why doesn't the following code compile (playground):
use std::collections::HashMap;
fn main() {
let mut h: HashMap<u32, u32> = HashMap::new();
h.insert(0, 0);
h.insert(1, h.remove(&0).unwrap());
}
The borrow checker complains that:
error[E0499]: cannot borrow `h` as mutable more than once at a time
--> src/main.rs:6:17
|
6 | h.insert(1, h.remove(&0).unwrap());
| - ------ ^ second mutable borrow occurs here
| | |
| | first borrow later used by call
| first mutable borrow occurs here
The code is safe, however, and an almost mechanical transformation of the last line makes it compile (playground):
//h.insert(1, h.remove(&0).unwrap());
let x = h.remove(&0).unwrap();
h.insert(1, x);
It was my understanding that this kind of issue got resolved with non-lexical lifetimes. This question is an example, and there are many others.
Is there some subtlety that makes the first variant incorrect after all, so Rust is correct to refuse it? Or is the NLL feature still not finished in all cases?
Your question also applies for a related case that may be more surprising — having the method call require &self when the method argument requires &mut self:
use std::collections::HashMap;
fn main() {
let mut h: HashMap<u32, u32> = HashMap::new();
h.insert(0, 0);
h.contains_key(&h.remove(&0).unwrap());
}
The Rust borrow checker uses what it calls two-phase borrows. An edited transcription of a chat I had with Niko Matsakis:
The idea of two-phase borrows is that the outer &mut is treated like an & borrow until it is actually used, more or less. This makes it compatible with an inner & because two & mix, but it is not compatible with an inner &mut.
If we wanted to support that, we'd have had to add a new kind of borrow -- i.e., an "unactivated" &mut wouldn't act like an &, it would act like something else (&const, maybe... "somebody else can mutate")
It's less clear that this is OK and it seemed to add more concepts so we opted not to support it.
As you stated, this is safe because the inner borrow is completed before the outer borrow starts, but actually recognizing that in the compiler is overly complex at this point in time.
See also:
Nested method calls via two-phase borrowing
Two-phase borrows in Guide to Rustc Development
Cannot borrow as immutable because it is also borrowed as mutable in function arguments
The Rust compiler first evaluates the calling object, then the arguments passed to it. That's why it first borrows the h.insert, then h.remove. Since the h is already borrowed mutably for insert, it denies the second borrow for remove.
This situation is not changed when using Polonius, a next-generation borrow checker. You can try it yourself with the nightly compiler:
RUSTFLAGS=-Zpolonius cargo +nightly run
The order of evaluation is similar in C++: https://riptutorial.com/cplusplus/example/19369/evaluation-order-of-function-arguments

Borrowed value does not live long enough when used by thread

So I'm pursuing my Rust adventures (loving it) and I'm exploring threads. As usual I stumbled upon an error that I do not understand.
Here is a minimal example:
use std::thread;
pub fn compute_something(input: &Vec<&usize>) -> usize {
input.iter().map(|v| *v).sum()
}
pub fn main() {
let items = vec![0, 1, 2, 3, 4, 5];
let mut slice: Vec<&usize> = Vec::new();
slice.push(&items[1]); // borrowed value does not live long enough
// argument requires that `items` is borrowed for `'static`
slice.push(&items[2]); // borrowed value does not live long enough
// argument requires that `items` is borrowed for `'static`
assert_eq!(3, compute_something(&slice));
let h = thread::spawn(move || compute_something(&slice));
match h.join() {
Ok(result) => println!("Result: {:?}", result),
Err(e) => println!("Nope: {:?}", e)
}
} // `items` dropped here while still borrowed
I have of course made a playground to illustrate.
If I drop the thread part (everything after the assert_eq! line) and just call compute_something(&slice) it compiles fine.
There are three main things I don't understand here:
Why is it a problem to drop items while borrowed at the end of the program, shouldn't the runtime clean-up the memory just fine? It's not like I'm gonna be able to access slice outside of main.
What is still borrowing items at the end of the program? slice? If so, why does that same program compile by just removing everything after the assert_eq! line? I can't see how it changes the borrowing pattern.
Why is calling compute_something from inside the thread's closure creating the issue and how do I solve it?
You move slice into the closure that you pass to thread::spawn(). Since the closure passed to thread::spawn() must be 'static, this implies that the vector being moved into the closure must not borrow anything that isn't 'static either. The compiler therefore deduces the type of slice to be Vec<&'static usize>.
But it does borrow something that's not 'static -- the values that you try to push into it borrow from a variable local to main(), and so the compiler complains about this.
The simplest way to fix this case is to have slice be a Vec<usize> and not borrow from items at all.
Another option is to use scoped threads from the crossbeam crate, which know how to borrow from local variables safely by enforcing that all threads are joined before a scope ends.
To directly answer the questions you posed:
Why is it a problem to drop items while borrowed at the end of the program, shouldn't the runtime clean-up the memory just fine?
When main() terminates, all threads are also terminated -- however, there is a brief window of time during which the values local to main() have been destroyed but before the threads are terminated. There can exist dangling references during this window, and that violates Rust's memory safety model. This is why thread::spawn() requires a 'static closure.
Even though you join the thread yourself, the borrow checker doesn't know that joining the thread ends the borrow. (This is the problem that crossbeam's scoped threads solve.)
What is still borrowing items at the end of the program?
The vector that was moved into the closure is still borrowing items.
Why is calling compute_something from inside the thread's closure creating the issue and how do I solve it?
Calling this function isn't creating the issue. Moving slice into the closure is creating the issue.
Here is the way I solved this issue.
I used Box::Leak: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.leak
let boxed_data = data.into_boxed_slice();
let boxed_data_static_ref = Box::leak(boxed_data);
let compressed_data = &boxed_data_static_ref[start_of_data..start_of_data+zfr.compressed_size as usize];
let handles = (0..NUM_THREADS).map(|thread_nr| {
thread::spawn(move || {
main2(thread_nr, compressed_data, zfr.crc);
})
}).collect::<Vec<_>>();
for h in handles {
h.join().unwrap();
}

Unreasonable "cannot borrow `a` as immutable because it is also borrowed as mutable"?

I have seen cannot borrow as immutable because it is also borrowed as mutable and my question is not a duplicate, since my code has Non-Lexical Lifetimes enabled.
I'm wondering if there is a fundamental reason why the following code:
fn f1(a: &u32) {
print!("{:?}", a);
}
fn main() {
let mut a = 3;
let b = &mut a;
f1(&a);
*b += 1;
print!("{:?}", b);
}
must result in the following error:
error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
--> src/bin/client/main.rs:91:6
|
90 | let b = &mut a;
| ------ mutable borrow occurs here
91 | f1(&a);
| ^^ immutable borrow occurs here
92 | *b += 1;
| ------- mutable borrow later used here
Now, I know that on the line f1(&a), we'll have one mutable reference (b) and one immutable reference (&a), and according to these rules this can't happen. But having 1 mutable and 1 immutable reference can only cause a problem if their usages are interleaved, right? That is, in theory, shouldn't Rust be able to observe that b is not used within &a's existence, and thus accept this program?
Is this just a limitation of the compiler? Or am I overlooking some other memory danger here?
That is, in theory, shouldn't Rust be able to observe that b is not used within &a's existence, and thus accept this program?
Maybe, though it's possible that there are edge cases where this would be a problem. I would expect optimisations to be an issue here e.g. eventually Rust will be able to finally tag &mut as noalias without LLVMs immediately miscompiling things, and in that case your code would be UB if it were allowed.
Is this just a limitation of the compiler?
In this case no, it's literally a limitation of the language specification. There are situations which are limitations of the compiler like loop mutations, but here you're trying to do something the language's rules explicitely and specifically forbid.
Even polonius will not change that.

Why are immutable borrows ok after mutable borrows in closures? [duplicate]

This question already has answers here:
What are non-lexical lifetimes?
(1 answer)
When should I add mut to closures?
(1 answer)
Closed 2 years ago.
While reading Steve Donovan's article Why Rust Closures are (Somewhat) Hard I stumbled upon this code snippet:
let mut x = 1.0;
let mut change_x = || x = 2.0;
change_x();
println!("{}", x);
Steve claims that the code should not compile because of this error:
previous borrow occurs due to use of x in closure
But when I run the code in the Rust Playground, the code works fine. It outputs "2".
I suppose that is because the closure change_x borrows the variable x mutably from the enclosing environment, but ...
Why does the immutable borrow in the println! macro work? x is mutably borrowed in the change_x closure defined above. Steve seems to suggest that this should be an error. Has the compiler been modified to handle this situation differently since the article was published?
The code doesn't compile when I remove the mut marker from change_x. Why?
Side note: the compiler version used by the Playground app is 1.48.0 at the time of posting this question.

Mutable borrow in function argument

Why doesn't the following code compile (playground):
use std::collections::HashMap;
fn main() {
let mut h: HashMap<u32, u32> = HashMap::new();
h.insert(0, 0);
h.insert(1, h.remove(&0).unwrap());
}
The borrow checker complains that:
error[E0499]: cannot borrow `h` as mutable more than once at a time
--> src/main.rs:6:17
|
6 | h.insert(1, h.remove(&0).unwrap());
| - ------ ^ second mutable borrow occurs here
| | |
| | first borrow later used by call
| first mutable borrow occurs here
The code is safe, however, and an almost mechanical transformation of the last line makes it compile (playground):
//h.insert(1, h.remove(&0).unwrap());
let x = h.remove(&0).unwrap();
h.insert(1, x);
It was my understanding that this kind of issue got resolved with non-lexical lifetimes. This question is an example, and there are many others.
Is there some subtlety that makes the first variant incorrect after all, so Rust is correct to refuse it? Or is the NLL feature still not finished in all cases?
Your question also applies for a related case that may be more surprising — having the method call require &self when the method argument requires &mut self:
use std::collections::HashMap;
fn main() {
let mut h: HashMap<u32, u32> = HashMap::new();
h.insert(0, 0);
h.contains_key(&h.remove(&0).unwrap());
}
The Rust borrow checker uses what it calls two-phase borrows. An edited transcription of a chat I had with Niko Matsakis:
The idea of two-phase borrows is that the outer &mut is treated like an & borrow until it is actually used, more or less. This makes it compatible with an inner & because two & mix, but it is not compatible with an inner &mut.
If we wanted to support that, we'd have had to add a new kind of borrow -- i.e., an "unactivated" &mut wouldn't act like an &, it would act like something else (&const, maybe... "somebody else can mutate")
It's less clear that this is OK and it seemed to add more concepts so we opted not to support it.
As you stated, this is safe because the inner borrow is completed before the outer borrow starts, but actually recognizing that in the compiler is overly complex at this point in time.
See also:
Nested method calls via two-phase borrowing
Two-phase borrows in Guide to Rustc Development
Cannot borrow as immutable because it is also borrowed as mutable in function arguments
The Rust compiler first evaluates the calling object, then the arguments passed to it. That's why it first borrows the h.insert, then h.remove. Since the h is already borrowed mutably for insert, it denies the second borrow for remove.
This situation is not changed when using Polonius, a next-generation borrow checker. You can try it yourself with the nightly compiler:
RUSTFLAGS=-Zpolonius cargo +nightly run
The order of evaluation is similar in C++: https://riptutorial.com/cplusplus/example/19369/evaluation-order-of-function-arguments

Resources