Mutable borrow in function argument - rust

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

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

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 does the borrow checker not understand that borrowing different parts of a slice is fundamentally okay?

The split_at_mut function (for splitting mutable slices at an index) requires unsafe code (according to the Rust book) to be implemented. But the book also says: "Borrowing different parts of a slice is fundamentally okay because the two slices aren’t overlapping".
My question: Why does the borrow checker not understand that borrowing different parts of a slice is fundamentally okay? (Is there a reason preventing the borrow checker from understanding this rule or is it just that it has not been implemented for some reason?)
Trying the suggested code with Rust 1.48 still results in the error shown in the book:
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
assert!(mid <= len);
(&mut slice[..mid], &mut slice[mid..])
}
fn main() {
let mut vector = vec![1, 2, 3, 4, 5, 6];
let (left, right) = split_at_mut(&mut vector, 3);
println!("{:?}, {:?}", left, right);
}
Gives error message:
error[E0499]: cannot borrow `*slice` as mutable more than once at a time
--> src/main.rs:6:30
|
1 | fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
| - let's call the lifetime of this reference `'1`
...
6 | (&mut slice[..mid], &mut slice[mid..])
| -------------------------^^^^^--------
| | | |
| | | second mutable borrow occurs here
| | first mutable borrow occurs here
| returning this value requires that `*slice` is borrowed for `'1`
Why does the borrow checker not understand that borrowing different parts of a slice is fundamentally okay?
Because it's impossible to do generally. Sure, in this case it's obvious that &slice[..mid] and &slice[mid..] are disjoint, but the complexity skyrockets once you escape trivial cases and it quickly becomes impossible.
These special trivial cases aren't implemented specially because:
Having a language feature that allows obviously disjoint slice borrows but not anything even slightly more complicated is unintuitive and can make beginners think they can do more things than they actually can
These special cases really only boil down to a couple patterns, most of which can be accomplished with split_at and its mutable counterpart
There's no way to reliably extend this to anything with the Index trait, which means that (&slice[..mid], &slice[mid..]) would be valid but (&vec[..mid], &vec[mid..]) wouldn't be, which is even more inconsistent. Of course this could be solved by making Vec a language intrinsic, but then what about VecDeque or user-defined data structures? It just leads to too much inconsistency that spirals into more inconsistency, which is something that Rust wants to avoid.
Rust’s borrow checker can’t understand that you’re borrowing different parts of the slice; it only knows that you’re borrowing from the same slice twice. What if the two slices are overlapping? There's no way to grantee that their not. (If there is, it isn't implemented yet)
That's why unsafe exists: when you know for sure that there's no way that your code will produce an unexpected behavior despite the compiler not being able to grantee that.

How to avoid multiple mutable borrows of a vector when inserting a value if the vector is empty?

In this github discussion you find this code that draws the ire of the borrow checker:
fn main() {
let mut vec = vec!();
match vec.first() {
None => vec.push(5),
Some(v) => unreachable!(),
}
}
I understand why having a mutation while immutable borrows are outstanding is problematic. I assumed that a solution was to explicitly only have one borrow (a mutable one) but it still resulted in my having two borrows, an immutable borrow and then a mutable borrow:
fn main() {
let mut vec: Vec<i32> = vec!();
let r_vec: &mut Vec<i32> = &mut vec;
match r_vec.first() {
None => r_vec.push(5),
Some(v) => unreachable!(),
}
}
The compiler is still not happy:
error[E0502]: cannot borrow `*r_vec` as mutable because it is also borrowed as immutable
--> testrust.rs:7:17
|
6 | match r_vec.first() {
| ----- immutable borrow occurs here
7 | None => r_vec.push(5),
| ^^^^^ mutable borrow occurs here
8 | Some(v) => unreachable!(),
9 | }
| - immutable borrow ends here
Why does my workaround not work, and what is the proper way to get around this issue?
You don't. Well, you "avoid" having multiple borrows by... not having multiple borrows.
fn main() {
let mut vec = vec![];
if vec.first().is_none() {
vec.push(5);
}
}
Even more idiomatically:
if vec.is_empty() {
vec.push(5);
}
In both cases, we borrow vec to make the method call, but terminate that borrow before the body of the if is executed. Compare that to the match where the borrow is made in the match head expression, and then shared with the match arms.
take one mutable borrow that can be used for both cases
That's not how it works. You have to understand how memory plays out and what a reference is. A Vec contains a pointer to memory where the data is stored.
When you get a reference to data the vector, the reference holds the address of the memory for the data, and the compiler ensures there's only one of those allowed to mutate the Vec. When you push a value, that may need to allocate new memory to store all the data. This may invalidate the reference you hold. If that were to occur, then the next time you use the reference, it would point to some other, unrelated piece of memory, your program would crash, your users data would be exposed to security vulnerabilities, etc. etc. etc.
The entire point of the issue you linked and the related pre-RFC is that this code should be able to be determined as safe:
match vec.first() {
None => vec.push(5),
Some(v) => unreachable!(),
}
In this case, the programmer can see that we never use the borrow in the None case, so the the compiler could theoretically end the borrow before executing any of the match arms or otherwise make the two arms disjoint with respect to lifetimes. It does not do that now.
However, in your version of code, it's actually worse. By explicitly taking the borrow and keeping it in a variable, you could be extending how long the borrow needs to stay around, forcing them to overlap.
Currently, the only solution is to reorder your code to artificially constrain borrows. I've not found this very annoying in practice, as usually there's a better organization of code anyway.
See also:
If let borrow conundrum
`if` condition remains borrowed in body
"cannot borrow as mutable more than once at a time" in if-else
Is there a way to release a binding before it goes out of scope?
match + RefCell = X does not live long enough
Any of the 90+ Rust questions that have the error message "mutable because it is also borrowed as immutable"

Borrow checker and function arguments in Rust, correct or over zealous? [duplicate]

This question already has an answer here:
Cannot borrow as immutable because it is also borrowed as mutable in function arguments
(1 answer)
Closed 4 years ago.
When a mutable argument is passed as a function argument, the borrow checker doesn't allow this to be used to construct other arguments, even when those arguments clone values without holding a reference.
While assigning variables outside the function is always an option1, logically this seems over zealous and something the borrow checker could take into account.
Is this working as intended or something that should be resolved?
#[derive(Debug)]
struct SomeTest {
pub some_value: f64,
pub some_other: i64,
}
fn some_fn(var: &mut SomeTest, other: i64) {
println!("{:?}, {}", var, other);
}
fn main() {
let mut v = SomeTest { some_value: 1.0, some_other: 2 };
some_fn(&mut v, v.some_other + 1);
// However this works!
/*
{
let x = v.some_other + 1;
some_fn(&mut v, x);
}
*/
}
Gives this error:
--> src/main.rs:14:21
|
14 | some_fn(&mut v, v.some_other + 1);
| - ^^^^^^^^^^^^ use of borrowed `v`
| |
| borrow of `v` occurs here
See: playpen.
[1]: Even though one-off assignments do sometimes improve readability, being forced to use them for arguments encourages use of scopes to avoid single use variables polluting the name-space, causing function calls that would otherwise be one line - being enclosed in braces and defining variables... I'd like to avoid this if possible especially when the requirement seems like something the borrow checker could support.
This is an artifact of the current implementation of the borrow checker. It is a well known limitation, dating back to at least 2013, and no one is overjoyed by it.
Is this working as intended
Yes.
something that should be resolved?
Yes.
The magic keywords are "non-lexical lifetimes". Right now, lifetimes are lexical - they correspond to the blocks of source that we type. Ideally, foo.method(foo.mutable_method()) would see that the borrow ends "inside the parenthesis", but for a myriad of reasons, it is tied to the entire statement.
For tons of further information see RFC issue 811 and everything linked from there.

Resources