Double mutable borrow in Rust `if let` statement [closed] - rust

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
Why does the following code coed in Rust yell a double mutable borrow error?
I would expect the first scope to be the first block since the returned values can't outlive this block, and the else if won't be called if the first if returned Some(arr).
if let Some(arr) = v.as_array_mut(){
...
} else if let Some(obj) = v.as_object_mut(){
...
} else {
...
}
|
178 | if let Some(arr) = v.as_array_mut(){
| -------------------------- first mutable borrow occurs here
...
195 | } else if let Some(obj) = v.as_object_mut(){
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| second mutable borrow occurs here
| first borrow later used here
BTW, breaking each call to two calls, which provides an exact similar behavior obviously doesn't yell an error:
if v.is_array(){
let arr = v.as_array_mut().unwrap();
...
} else if v.is_object(){
let obj = v.as_object_mut().unwrap();
...
} else {
...
}

This has to do with non-lexical lifetimes.
Extend Rust's borrow system to support non-lexical lifetimes -- these are lifetimes that are based on the control-flow graph, rather than lexical scopes. The RFC describes in detail how to infer these new, more flexible regions, and also describes how to adjust our error messages. The RFC also describes a few other extensions to the borrow checker, the total effect of which is to eliminate many common cases where small, function-local code modifications would be required to pass the borrow check. (The appendix describes some of the remaining borrow-checker limitations that are not addressed by this RFC.)
1.36 has NLL turned on for the 2015 edition. The 2018 edition enables them by default.
So in your case, I guess you are on a version not supporting NLL.

Related

Can't change element in vector [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 9 months ago.
Improve this question
I found this answer to a related question, but it does not help me. According to my understanding the following code should work:
container_options is not just a mutable reference, it is owned. I should be able to extract a mutable reference to it or even just edit it directly, but the compiler refuses to accept anything I tried. I have no idea where the problem is and hope that someone else does.
Edit: I kinda found a workaround, so this is solved.
fn update_container_options_with_paths(
container_options: Vec<ContainerOption>,
path_options: &Vec<Rc<PathOption>>,
) {
for container_index in 0..container_options.len() {
let container_option = &mut container_options[container_index];
container_option.compatible_path_options = 0;
for (path_index, path_option) in path_options.iter().enumerate() {
if PathOption::decode_loading_mask(
path_option.summary.compatible_container_options,
container_index,
) {
container_option.set_compatible(path_index);
}
}
}
}
I should be able to extract a mutable reference to it or even just edit it directly, but the compiler refuses to accept anything I tried. I have no idea where the problem is and hope that someone else does.
The compiler tells you exactly what the issue is and how to fix it:
error[E0596]: cannot borrow `container_options` as mutable, as it is not declared as mutable
--> src/lib.rs:5:37
|
3 | fn update_container_options_with_paths(container_options: Vec<()>, path_options: &Vec<Rc<()>>) {
| ----------------- help: consider changing this to be mutable: `mut container_options`
4 | for container_index in 0..container_options.len() {
5 | let container_option = &mut container_options[container_index];
| ^^^^^^^^^^^^^^^^^ cannot borrow as mutable
For more information about this error, try `rustc --explain E0596`.
Even when something is owned, Rust still has a concept of mutable v non-mutable bindings, and you can only get a mutable reference from a mutable binding.
This is mostly indicative as you can trivially change it e.g.
let mut a = a;
but it's still non optional.
And I don't understand the purpose of indexing into container_options, why not just container_options.iter_mut() or (iterating on) &mut container_options? It's not like it works any less than index_mut.

The concept of rust lifetime

Question
I usually use c++ lang, and recently I'm learning rust lang but now confusing the concept of lifetime.
My understanding for lifetime is as follows. Is this correct?
Lifetime is an attribute of instance.
Lifetime represents the valid scope of instance.
Background of above question
The following code is a sample code at here.
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
The document said that 'a is a lifetime, and 'b is also a lifetime.
but if my understanding is correct, 'a is not lifetime, just scope of symbol r... Is `a really lifetime?
P.S.
There are two things named "a lifetime": value's lifetime, and the lifetime attached to a reference.
Thank you! Maybe I understood a little bit more than before...
A lifetime is not so much an attribute of an instance, it's more the opposite way around; when an instance is borrowed (a reference is taken), it is borrowed for a specific lifetime. The borrow checker will try to minimise the lifetime of the borrow so that it can be as permissive as possible whilst ensuring code is still safe.
Lifetimes are your way of communicating how long you need a reference to last to the compiler, and given that information, the compiler will check that nothing violates that rule (by holding the reference for longer), and that the reference is available for at least as long as you require it. This can be thought of in much the same way that a compiler can check type to ensure you don't assign a float to an integer.
Lifetimes are also independent of scope. Rust now has non-lexical lifetimes (https://github.com/rust-lang/rfcs/pull/2094 — see What are non-lexical lifetimes? for more detailed explanation), meaning that within a given scope, the borrow checker is capable of determining that borrows have shorter lifetimes than the containing scope.

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 did compiler not error on this mutable borrow when there is an immutable borrowed string slice reference still in scope?

I am learning Rust from The Rust Programming Language book available from No Starch Press but ran into an issue where the compiler did not behave as explained in the book in chapter 4 on p. 77.
Chapter 4 of the book is discussing ownership, and the example on p. 77 is similar to this without the final println!() in main() (I've also added comments and the function from p. 76 to create an MCVE). I also created a playground.
fn main() {
let mut s = String::from("Hello world!");
let word = first_word(&s);
// according to book, compiler should not allow this mutable borrow
// since I'm already borrowing as immutable, but it does allow it
s.clear();
// but of course I do get error here about immutable borrow later being
// used here, but shouldn't it have errored on the clear() operation before
// it got here?
println!("First word of s is \"{}\"", word);
}
// return string slice reference to first word in string or entire string if
// no space found
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}
I understand why the compiler throws an error where it currently does. But my understanding from the book is that it should have caused a compiler error when I tried to clear the string because I cannot borrow s as mutable because it is also borrowed as immutable, thus eliminating the possibility of the error I received (i.e., it should not compile even without my final println!()). But it compiles fine for me so long as I don't try to use the reference to word after the clear() operation.
The book is using Rust 1.21.0 (see p. 2) whereas I am using Rust 1.31.0—so this is likely a change that has been introduced to the compiler, but I am trying to understand why. Why is it better to error as it currently does versus where the book said it would error?
To be clear, I understand the errors themselves. I'm trying to understand why it doesn't throw a compiler error in the location the book says it should (i.e., why the change in compiler behavior?).
This is a change due to the non-lexical lifetimes, the update made in the latest versions of Rust (stabilized in the 2018 edition introduced with Rust 1.31, if I'm not mistaken).
In earlier versions of Rust (including the one on which the book is based), any reference was supposed to be alive for the whole scope, where it was created (that is, until the end of the enclosing braces). If you removed the line using word and try to compile the code on the old version, it would issue the same error — "borrowed as mutable while borrowed as immutable".
Now, the borrow checker tracks whether the reference is really used. If you weren't using word after s.clear(), it would be assumed that the immutable reference to s can be safely dropped before s.clear() takes a mutable one, so, as you've mentioned, this code will be safely compiled. When the println! is there, the borrow checker sees that the immutable and mutable borrows' scoped are intersecting, and tells you exactly that — note that the error is divided in three parts:
start of immutable borrow,
start of mutable borrow,
usage of immutable borrow.

"cannot borrow as mutable more than once at a time" in if-else [duplicate]

This question already has answers here:
Why is a borrow still held in the else block of an if let?
(5 answers)
Closed 6 years ago.
I'm writing a program to calculate the frequency of word occurrence. This is a segment of my code.
// hm is a HashMap<&str, u32>
if let Some(val) = hm.get_mut(tt) {
*val += 1u32;
} else {
hm.insert(tt.clone(), 1u32);
}
And I got...
error: cannot borrow `hm` as mutable more than once at a time [E0499]
hm.insert(tt.clone(), 1u32);
^~
note: first mutable borrow occurs here
if let Some(val) = hm.get_mut(tt) {
^~
note: first borrow ends here
}
^
help: run `rustc --explain E0499` to see a detailed explanation
I can bypass this by moving hm.insert() out of else scope but it's kind of "un-programmatic" way... I tried using match but the same error (would obviously) happened.
How can I fix this?
This is a common problem with HashMaps in Rust: borrows can't have ragged edges. Fortunately, there is an API to handle this case.
You can use HashMap::entry() to get a place on a hash map, occupied or vacant and then use or_insert() to set a value for the key if there is not one already.
*hm.entry(tt).or_insert(0u32) += 1;
This returns a reference to the hm location, filling it with 0 if it wasn't there, then increments whatever it was.
That Rust's lifetimes are prone to gratuitous conflicts is not an unknown issue. Here is a Rust's Rust core team member discussing plans to tackle this in a future version of Rust. For now, though, there are library methods that work around it.

Resources