I don't understand this one,
why is the compiler error on let co = 0; co += &1; cannot borrow co as mutable
but errors on let co = 0; co += 1; cannot assign twice to immutable variable co?
why is co borrowed ?
I expect co to not be borrowed
Interesting that the two examples show such different error messages, but not meaningfully interesting since they both error-out on the same basic principle: co is not marked as mutable, so you can't mutate it with +=.
The latter error message looks more specialized and therefore hopes to be more helpful by directly addressing the problem of assignment. The former error message appears to be the more general "cannot borrow _ as mutable" which applies more widely.
Why is co borrowed? I expect co to not be borrowed.
The implementation of += is done through the AddAssign trait which needs to accept the left-hand side as a mutable reference in order to mutate it. That is why the borrow happens, but the error of course indicates that a mutable borrow cannot be created since co is not mutable.
Related
I'm having difficulty understanding why Rust's borrow checker does not allow multiple mutable borrows when it is safe to do so.
Let's give an example:
fn borrow_mut(s : &mut String) {
s.push_str(" world!");
println!("{}", s);
}
fn main() {
let mut s = String::from("hello");
let rs : &mut String = &mut s;
// second mutable borrow
borrow_mut(&mut s);
println!("{rs}");
}
This code fails to compile with the following message:
error[E0499]: cannot borrow `s` as mutable more than once at a time
--> main.rs:11:16
|
8 | let rs : &mut String = &mut s;
| ------ first mutable borrow occurs here
...
11 | borrow_mut(&mut s);
| ^^^^^^ second mutable borrow occurs here
12 |
13 | println!("{rs}");
| -- first borrow later used here
rs points on the variable of type String in the stack frame. String contains pointer on memory in the heap. So even if the string reallocates its data in borrow_mut(), both pointers are still valid, so this code should be safe.
Could someone explain the reason of why the borrow checker prevents multiple mutable borrows even when it's safe?
It's for thread safety, to avoid data races. If two such mutable borrowings can exist, then two threads of execution can both attempt to modify the original data. If they do, all sorts of nasty race conditions can arise, e.g. if both threads try to append to the string:
The underlying array holding the data can get reallocated twice, with one of them leaked
The appended data could end up writing out of bounds due to time-of-check/time-of-use issues
You could end up with inconsistent definitions of the length and capacity
On some architectures and data sizes, tearing could mean a single logical value is read half as the old version and half as the updated value (producing something that could easily be unrelated to either the old or new value)
etc.
Borrows as a language feature mean that the function can temporarily hand off its unique mutable-ownership to some other function; while that other function holds the borrow, the original object can't be accessed through anything but that mutable borrow. It also means that for non-mutable borrows, it can prevent mutable borrows that might causes races between reads through the non-mutable borrow and writes through the mutable borrow. The borrow checker is preventing you from launching a thread that modifies s, then calling borrow_mut from the main thread, and the two threads producing garbage or crashing the program when they modify s simultaneously.
To be clear, with an advanced borrow-checker in some future version of Rust, this code could be made to work (the code you wrote does nothing inherently unsafe). But fully analyzing deep code paths to ensure nothing evil could possibly occur is hard, and it's relatively easy to impose stricter rules (which might be loosened in the future if they're sure it won't impose restrictions on the language design that bite them later). Your code would work just fine if you passed the single mutable borrow you already had into borrow_mut after all; your code is not made worse by doing things The Rust Way™.
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.
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.
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.
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.