Can someone explain the following, please?
this compiles (explanation: NLL y not referenced after initial definition?)
fn main() {
let mut x = 5;
let y = &x;
let z = &mut x;
println!("z: {}", z);
}
this doesn't compile (explanation: z not referenced but only introduced the line before so still active?)
fn main() {
let mut x = 5;
let y = &x;
let z = &mut x;
println!("y: {}", y);
}
this compiles (explanation: NLL z not referenced after initial definition?)
fn main() {
let mut x = 5;
let z = &mut x;
let y = &x;
println!("y: {}", y);
}
this doesn't compile (just to see whether introducing lines would lead to z not being active by the println)
fn main() {
let mut x = 5;
let y = &x;
let z = &mut x;
let foo = String::from("foo");
println!("y: {}, foo: {}", y, foo);
}
I'm confused... I couldn't find anything that covers this specific case in the book but if somebody has a link to something that explains this behaviour, I would appreciate it.
fn main() {
let mut x = 5;
let y = &x;
// 'y lifetime starts, holding an immutable reference to x
// 'y lifetime ends (never used later), releasing hold on x
let z = &mut x; // this is fine because the hold is released
println!("z: {}", z);
}
fn main() {
let mut x = 5;
let y = &x;
// 'y lifetime starts, holding an immutable reference to x
let z = &mut x; // this is forbidden because of the existing hold
println!("y: {}", y);
// 'y lifetime ends, releasing hold on x
}
fn main() {
let mut x = 5;
let z = &mut x;
// 'z lifetime starts, holding a mutable reference to x
// 'z lifetime ends (never used later), releasing hold on x
let y = &x; // this is fine because the hold is released
println!("y: {}", y);
// 'y lifetime ends, releasing hold on x
}
Pretty much the same as #2
fn main() {
let mut x = 5;
let y = &x;
// 'y lifetime starts, holding an immutable reference to x
let z = &mut x; // this is forbidden because of the existing hold
let foo = String::from("foo");
println!("y: {}, foo: {}", y, foo);
// 'y lifetime ends, releasing hold on x
}
As you know, no other references can exist while a mutable reference to some data also exists. You will see &mut also called "exclusive references" for this reason.
The borrow checker is what enforces this, using the lifetimes of the references in your code. In older versions of Rust, all lifetimes were "lexical" - they would last as long as their containing scope. "Non-lexical lifetimes" were introduced to make things easier for the programmer, by making it so reference lifetimes would only last as long as those references were used.
This is what allows examples #1 and #3 to work. A reference is created but the lifetime immediately ends because they aren't used later, so there is no overlap of lifetimes when the next line creates a different reference.
It's useful to look at the lifetime of every variable here. Let's look example by example.
// example one
fn main() {
let mut x = 5;
let y = &x; // y is declared here, but never used
// so its lifetime is effectively nil
let z = &mut x; // by this line, it no longer exists
println!("z: {}", z);
}
// example two
fn main() {
let mut x = 5;
let y = &x; // y is declared here and used in the
// println, so...
let z = &mut x; // ...this is invalid, since you cannot
// take a mutable reference when you have
// an existing reference.
println!("y: {}", y);
}
// example three
fn main() {
let mut x = 5;
let z = &mut x; // z is declared here but never used
// so its lifetime is effectively nil
let y = &x; // by this line, it no longer exists
println!("y: {}", y);
}
// example four
fn main() {
let mut x = 5;
let y = &x;
let z = &mut x; // This is functionally identical to ex. 2
let foo = String::from("foo");
println!("y: {}, foo: {}", y, foo);
}
Related
The following program_pass compiles in Rust.
fn main() {
let mut x = 0;
let mut y = &mut x;
let mut z = &mut y;
let mut last = &mut z;
let mut alt_y = &mut x;
let mut alt_z = &mut alt_y;
z = &mut alt_y; // *last = &mut alt_y;
}
The following program_error does not.
fn main() {
let mut x = 0;
let mut y = &mut x;
let mut z = &mut y;
let mut last = &mut z;
let mut alt_y = &mut x;
let mut alt_z = &mut alt_y;
*last = &mut alt_y; // z = &mut alt_y;
}
What is violated in program_error, while not in program_pass?
Just started, but this is really going against what I thought to be my understanding of Rust.
It's not inconsistency, it is is a intended behavior instead.
In first case, no mutable reference is being used. Effectively no issue, so rust compiler is happy.
In second case the rust compiler sees that mutable reference last is being dereferenced so it is taken as value access. And as we know rust doesn't allow the two mutable borrows.
Reference: Deref
To prove my point have tweaked your program a bit
fn main() {
let mut x = 0;
let mut y = &mut x;
let mut z = &mut y;
let mut last = &mut z;
let mut alt_y = &mut x;
let mut alt_z = &mut alt_y;
// notice the RHS here assigning
// mutable reference of i32 literal
*last = &mut &mut 4;
// ^ not related to x anyhow
}
And now the error will reveal the reason behind the issue\
error[E0499]: cannot borrow `x` as mutable more than once at a time
--> src/main.rs:7:21
|
3 | let mut y = &mut x;
| ------ first mutable borrow occurs here
...
7 | let mut alt_y = &mut x;
| ^^^^^^ second mutable borrow occurs here
...
11 | *last = &mut &mut 4;
| ------------------- first borrow later used here
I'm working through the ownership section.
I wrote the following code an expected an error.
Question: Why isn't there an error in the first compiling version?
This one compiles:
fn main () {
let mut x = String::from("vier");
let mut y = x.to_owned()+"1"; //here x should go out of scope, because used in a method
let mut z = x.to_owned()+"2";
x.push_str("test");
println!("{}", x);
println!("{}", y);
println!("{}", z);
}
This one does not compile:
fn main () {
let mut x = String::from("vier");
let mut y = x;//x out of scope
let mut z = x;//x out of scope
x.push_str("test");
println!("{}", x);
println!("{}", y);
println!("{}", z);
}
to_owned() only takes a reference, not ownership. You'll notice that the blanket implementation for T requires T: Clone:
impl<T> ToOwned for T where
T: Clone,
type Owned = T
That indicates that .to_owned() uses .clone() for any type that implements Clone.
to_owned is a method from borrow::ToOwned trait. Which returns an owned version (cloned/copy) of the element. It just takes an &self so the object is not really moved.
On Rust Book there's this lifetime example:
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let x; // -+ x goes into scope
// |
{ // |
let y = &5; // ---+ y goes into scope
let f = Foo { x: y }; // ---+ f goes into scope
x = &f.x; // | | error here
} // ---+ f and y go out of scope
// |
println!("{}", x); // |
}
Simplifying this to:
fn main() {
let x;
{
let y = 42;
x = &y;
}
println!("The value of 'x' is {}.", x);
}
Is it possible to have it work without clone?
In this example, you can simply enlarge the lifetime of y by removing the scope introduced by the curly braces.
fn main() {
let x;
let y = 42;
x = &y;
println!("The value of 'x' is {}.", x);
}
Or, as others suggested, you can transfer the ownership from y to x, i.e. move the value from y to x, by removing the reference operator & from y:
fn main() {
let x;
{
let y = 42;
x = y;
}
println!("The value of 'x' is {}.", x);
}
The following code doesn't compile, because x is used after move (since x has type &mut u8, which does not implement the Copy trait)
fn main() {
let mut a: u8 = 1;
let x: &mut u8 = &mut a;
let y = x;
x;
}
And as I understand y implicitly has type &mut u8
But if I specify the type of y explicitly it compiles. The following code compiles
fn main() {
let mut a: u8 = 1;
let x: &mut u8 = &mut a;
let y: &mut u8 = x;
x;
}
By the way if I change let y: &mut u8 = x; to let y: &u8 = x; it also compiles.
It seems to me nothing has changed, y is &mut u8 in the first example and it is &mut u8 in the second but the former doesn't compile and the latter does compile.
Why? What's the difference?
&mut u8 is not a complete type. All reference types must have a lifetime parameter on them. When you elide it, the Rust compiler must infer it, and it will pick the shortest lifetime possible. In your first example, the first step of desugaring is (using a fake syntax from the Rustonomicon):
fn main() {
let mut a: u8 = 1;
'a: {
let x: &'a mut u8 = &'a mut a; // the lifetime of x must last until it's use at the end
let y = x;
x;
}
}
This is still not completely explicit, since the type of y is still not known. What is it? Well, since it's being assigned from x, which is a &'a mut u8, it should also be a &'a mut u8. Note that this is not following the "shortest lifetime possible" rule of lifetime elision. You didn't elide a lifetime, you elided the whole type, which is reconstructed by type inference.
fn main() {
let mut a: u8 = 1;
'a: {
let x: &'a mut u8 = &'a mut a; // the lifetime of x must last until its use at the end
let y: &'a mut u8 = x;
x;
}
}
Well, that's no good. Since y has the same lifetime as x, its creation involves moving the reference in x and making x invalid. The program is thus rejected for trying to use x.
Adding the signature to y essentially gives the compiler a new place where it can infer lifetimes. Before, normal type inference made y have the same type as x, which meant it lasted as long as x and made x unusable. Now, y doesn't need to have the same type as x; the lifetime of the borrow can be different. In particular, it is made shorter.
fn main() {
let mut a: u8 = 1;
'a: {
let x: &'a mut u8 = &'a mut a; // the lifetime of x must last until it's use at the end
'b: {
let y: &'b mut u8 = x; // y and x can now have different lifetimes, *x is reborrowed here
}
x; // x not moved from, still valid
}
}
Now, instead of moving the reference x into y and making it invalid, the value *x is temporarily "reborrowed" to make y, as by let y: &'b mut u8 = &'b mut *x.
The other possible fix is to explicitly say "borrow *x again with a different lifetime":
fn main() {
let mut a: u8 = 1;
let x: &mut u8 = &mut a;
let y = &mut *x;
x;
}
The principle is the same as before: saying & more often gives the compiler more places where it can massage the lifetimes in the program to make everything work.
This code works:
let stdin = std::io::stdin();
let mut rdr = csv::Reader::from_reader(stdin);
let mut hmap = HashMap::<String, u64>::new();
rdr.records()
.map(|r| r.unwrap())
.fold((), |_, item| {
// TODO: Is there a way not to have to copy item[col] every time?
let counter = hmap.entry(item[col].to_string()).or_insert(0);
*counter += 1;
});
This code fails with the message: "cannot move out of acc because it is borrowed"
let stdin = std::io::stdin();
let mut rdr = csv::Reader::from_reader(stdin);
let hmap = rdr.records()
.map(|r| r.unwrap())
.fold(HashMap::<String, u64>::new(), |mut acc, item| {
// TODO: Is there a way not to have to copy item[col] every time?
let counter = acc.entry(item[col].to_string()).or_insert(0);
*counter += 1;
acc
});
You cannot return acc from the closure because you have a mutable borrow to it that still exists (counter).
This is a limitation of the Rust compiler (specifically the borrow checker). When non-lexical lifetimes are enabled, your original code will work:
#![feature(nll)]
use std::collections::HashMap;
fn main() {
let hmap = vec![1, 2, 3].iter().fold(HashMap::new(), |mut acc, _| {
let counter = acc.entry("foo".to_string()).or_insert(0);
*counter += 1;
acc
});
println!("{:?}", hmap);
}
Before NLL, the compiler is overly conservative about how long a borrow will last. To work around this, you can introduce a new scope to constrain the mutable borrow:
use std::collections::HashMap;
fn main() {
let hmap = vec![1, 2, 3].iter().fold(HashMap::new(), |mut acc, _| {
{
let counter = acc.entry("foo".to_string()).or_insert(0);
*counter += 1;
}
acc
});
println!("{:?}", hmap);
}
You can also prevent the borrow from lasting beyond the line it's needed in:
use std::collections::HashMap;
fn main() {
let hmap = vec![1, 2, 3].iter().fold(HashMap::new(), |mut acc, _| {
*acc.entry("foo".to_string()).or_insert(0) += 1;
acc
});
println!("{:?}", hmap);
}
I assumed Rust would know that counter would go out of scope once acc was returned
This is understandable and relates to the non-lexical lifetimes discussion. The "good" news is that Rust is being consistent about how references work when the thing being referenced moves. In this case, you are moving the accumulator into an "output slot". You can see this with plain functions as well:
fn foo(mut s: Vec<u8>) -> Vec<u8> {
let borrow = &mut s[0];
s
}
fn main() {}
But really, it's the same as moving a referred-to variable at all:
fn main() {
let mut s = Vec::<u8>::new();
let borrow = &mut s[0];
let s2 = s;
}
Both of these fail before NLL and work afterwards.