This code doesn't compile:
fn main() {
let x = "".to_string();
let y = &x;
let z = *y;
}
Compiler error output is:
cannot move out of *y which is behind a shared reference
move occurs because *y has type String, which does not implement the Copy trait
I'm not very clear what's happening here and looking for an explanation.
I was expecting z taking ownership of the String and x and y becoming unusable.
let z = *y;
This line only knows y, and y is a &String. &String doesn't carry any information where it comes from, it only carries the information that it is a reference to a String. It doesn't know or care about the fact that x contains the actual content, nor does it have any control over x, apart of the fact that the borrow checker makes sure that x stays in scope and immutable.
So *y doesn't actually produce x, but an anonymous String value that is only accessible through a reference, meaning it can be used, but not owned.
By doing z = *y, you are attempting to own the value behind a reference. But as I said, this would require modifying x (as it isn't valid any more afterwards), and y has no power over x. So this isn't possible.
Because doing z = *y wouldn't be a problem with copyable types, as they don't require a transfer of ownership but simply get copied, Rust informs you that this isn't possible because the value that y references doesn't implement Copy.
Related
fn main() {
let mut name = String::from("Charlie");
let x = &mut name;
let y = x; // x has been moved
say_hello(y);
say_hello(y); // but y has not been moved, it is still usable
change_string(y);
change_string(y);
}
fn say_hello(s: &str) {
println!("Hello {}", s);
}
fn change_string(s: &mut String) {
s.push_str(" Brown");
}
When I assign x to y x has been moved. However, I would expect something with move semantics to be moved when I use it in a function. However, I can still use the reference after subsequent calls. Maybe this has to do with say_hello() taking a immutable reference but change_string() takes a mutable reference but the reference is still not moved.
You are completely right with both your reasoning and your observations. It definitely looks like things should be happening the way you describe it. However, the compiler applies some convenience magic here.
Move semantics generally apply in Rust for all types that do not implement the Copy trait. Shared references are Copy, so they are simply copied when assigned or passed to a function. Mutable references are not Copy, so they should be moved.
That's where the magic starts. Whenever a mutable reference is assigned to a name with a type already known to be a mutable reference by the compiler, the original reference is implicitly reborrowed instead of being moved. So the function called
change_string(y);
is transformed by the compiler to mean
change_string(&mut *y);
The original reference is derefenced, and a new mutable borrow is created. This new borrow is moved into the function, and the original borrow gets released once the function returns.
Note that this isn't a difference between function calls and assignments. Implicit reborrows happen whenever the target type is already known to be a mutable reference by the compiler, e.g. because the pattern has an explicit type annotation. So this line also creates an implicit reborrow, since we explicitly annotated it as a mutable reference type:
let y: &mut _ = x;
This function call on the other hand moves (and thus consumes) the mutable reference y:
fn foo<T>(_: T) {}
[...]
foo(y);
The generic type T here isn't explicitly a mutable reference type, so no implicit reborrow occurs, even though the compiler infers that the type is a mutable reference – just as in the case of your assignment let y = x;.
In some cases, the compiler can infer a generic type is a mutable reference even in the absence of an explicit type annotation:
fn bar<T>(_a: T, _b: T) {}
fn main() {
let mut i = 42;
let mut j = 43;
let x = &mut i;
let y = &mut j;
bar(x, y); // Moves x, but reborrows y.
let _z = x; // error[E0382]: use of moved value: `x`
let _t = y; // Works fine.
}
When inferring the type of the first parameter, the compiler doesn't know yet it's a mutable reference, so no implicit reborrow occurs and x is moved into the function. However, when reaching the second parameter, the compiler has already inferred that T is a mutable reference, so y is implicitly reborrowed. (This example is a good illustration why adding compiler magic to make things "just work" generally is a bad idea. Explicit is better than implicit.)
Unfortunately, this behaviour currently isn't documented in the Rust reference.
See also:
Stuff the Identity Function Does (in Rust)
Discussion of the topic on the Rust users forum
Why is the mutable reference not moved here?
fn main() {
let mut name = String::from("Charlie");
let x = &mut name;
let y = x; // x has been moved
say_hello(y);
say_hello(y); // but y has not been moved, it is still usable
change_string(y);
change_string(y);
}
fn say_hello(s: &str) {
println!("Hello {}", s);
}
fn change_string(s: &mut String) {
s.push_str(" Brown");
}
When I assign x to y x has been moved. However, I would expect something with move semantics to be moved when I use it in a function. However, I can still use the reference after subsequent calls. Maybe this has to do with say_hello() taking a immutable reference but change_string() takes a mutable reference but the reference is still not moved.
You are completely right with both your reasoning and your observations. It definitely looks like things should be happening the way you describe it. However, the compiler applies some convenience magic here.
Move semantics generally apply in Rust for all types that do not implement the Copy trait. Shared references are Copy, so they are simply copied when assigned or passed to a function. Mutable references are not Copy, so they should be moved.
That's where the magic starts. Whenever a mutable reference is assigned to a name with a type already known to be a mutable reference by the compiler, the original reference is implicitly reborrowed instead of being moved. So the function called
change_string(y);
is transformed by the compiler to mean
change_string(&mut *y);
The original reference is derefenced, and a new mutable borrow is created. This new borrow is moved into the function, and the original borrow gets released once the function returns.
Note that this isn't a difference between function calls and assignments. Implicit reborrows happen whenever the target type is already known to be a mutable reference by the compiler, e.g. because the pattern has an explicit type annotation. So this line also creates an implicit reborrow, since we explicitly annotated it as a mutable reference type:
let y: &mut _ = x;
This function call on the other hand moves (and thus consumes) the mutable reference y:
fn foo<T>(_: T) {}
[...]
foo(y);
The generic type T here isn't explicitly a mutable reference type, so no implicit reborrow occurs, even though the compiler infers that the type is a mutable reference – just as in the case of your assignment let y = x;.
In some cases, the compiler can infer a generic type is a mutable reference even in the absence of an explicit type annotation:
fn bar<T>(_a: T, _b: T) {}
fn main() {
let mut i = 42;
let mut j = 43;
let x = &mut i;
let y = &mut j;
bar(x, y); // Moves x, but reborrows y.
let _z = x; // error[E0382]: use of moved value: `x`
let _t = y; // Works fine.
}
When inferring the type of the first parameter, the compiler doesn't know yet it's a mutable reference, so no implicit reborrow occurs and x is moved into the function. However, when reaching the second parameter, the compiler has already inferred that T is a mutable reference, so y is implicitly reborrowed. (This example is a good illustration why adding compiler magic to make things "just work" generally is a bad idea. Explicit is better than implicit.)
Unfortunately, this behaviour currently isn't documented in the Rust reference.
See also:
Stuff the Identity Function Does (in Rust)
Discussion of the topic on the Rust users forum
Why is the mutable reference not moved here?
fn main() {
let mut name = String::from("Charlie");
let x = &mut name;
let y = x; // x has been moved
say_hello(y);
say_hello(y); // but y has not been moved, it is still usable
change_string(y);
change_string(y);
}
fn say_hello(s: &str) {
println!("Hello {}", s);
}
fn change_string(s: &mut String) {
s.push_str(" Brown");
}
When I assign x to y x has been moved. However, I would expect something with move semantics to be moved when I use it in a function. However, I can still use the reference after subsequent calls. Maybe this has to do with say_hello() taking a immutable reference but change_string() takes a mutable reference but the reference is still not moved.
You are completely right with both your reasoning and your observations. It definitely looks like things should be happening the way you describe it. However, the compiler applies some convenience magic here.
Move semantics generally apply in Rust for all types that do not implement the Copy trait. Shared references are Copy, so they are simply copied when assigned or passed to a function. Mutable references are not Copy, so they should be moved.
That's where the magic starts. Whenever a mutable reference is assigned to a name with a type already known to be a mutable reference by the compiler, the original reference is implicitly reborrowed instead of being moved. So the function called
change_string(y);
is transformed by the compiler to mean
change_string(&mut *y);
The original reference is derefenced, and a new mutable borrow is created. This new borrow is moved into the function, and the original borrow gets released once the function returns.
Note that this isn't a difference between function calls and assignments. Implicit reborrows happen whenever the target type is already known to be a mutable reference by the compiler, e.g. because the pattern has an explicit type annotation. So this line also creates an implicit reborrow, since we explicitly annotated it as a mutable reference type:
let y: &mut _ = x;
This function call on the other hand moves (and thus consumes) the mutable reference y:
fn foo<T>(_: T) {}
[...]
foo(y);
The generic type T here isn't explicitly a mutable reference type, so no implicit reborrow occurs, even though the compiler infers that the type is a mutable reference – just as in the case of your assignment let y = x;.
In some cases, the compiler can infer a generic type is a mutable reference even in the absence of an explicit type annotation:
fn bar<T>(_a: T, _b: T) {}
fn main() {
let mut i = 42;
let mut j = 43;
let x = &mut i;
let y = &mut j;
bar(x, y); // Moves x, but reborrows y.
let _z = x; // error[E0382]: use of moved value: `x`
let _t = y; // Works fine.
}
When inferring the type of the first parameter, the compiler doesn't know yet it's a mutable reference, so no implicit reborrow occurs and x is moved into the function. However, when reaching the second parameter, the compiler has already inferred that T is a mutable reference, so y is implicitly reborrowed. (This example is a good illustration why adding compiler magic to make things "just work" generally is a bad idea. Explicit is better than implicit.)
Unfortunately, this behaviour currently isn't documented in the Rust reference.
See also:
Stuff the Identity Function Does (in Rust)
Discussion of the topic on the Rust users forum
Why is the mutable reference not moved here?
I'm working on my understanding of Rust's ownership- and borrowing-model and am confused by the following:
let mut x: i32 = 1;
let ref_x = &mut x;
let refref_x = &mut *ref_x;
*refref_x = 2;
*ref_x = 3;
To my knowledge, I am effectively creating two separate mutable references to x. Why is this code legal when avoiding the indirection through pointers and just changing line 3 to
let refref_x = &mut x;
is obviously not?
Am I misunderstanding central concepts or is the compiler adding some magic in the background?
To my knowledge, I am effectively creating two separate mutable references to x.
Nope, the second reference borrows from (and thus locks out) the first one, as you can see if you try to use the first one while the second one is still live:
let mut x: i32 = 1;
let ref1 = &mut x;
let ref2 = &mut *ref1;
*ref1 = 3; // <- can not assign because ref1 is borrowed (by the previous line)
*ref2 = 2;
ref2 is a reborrow of ref1. It's unsurprising that you'd be confused though as this is an ill-documented feature, though a pretty essential one.
Am I misunderstanding central concepts or is the compiler adding some magic in the background?
a bit of both, basically &[mut]* is a "special form" of the language, it doesn't dereference the value then re-reference it completely separately, instead it "reinterprets" the pointer.
Incidentally, this will likely lead to even more confusion the first time you encounter forced moves.
I was surprised by the result of these two apparently similar programs.
fn main() {
let y: &int = &31i;
println!("My number is {}.",*y)
}
//Output
My number is 31.
However, this code gives me an error.
fn main() {
let y: ∫
y = &31i;
println!("My number is {}.",*y)
}
// Output on Rust Playpen
3:12 error: borrowed value does not live long enough
5:2 note: reference must be valid for the block at 1:10...
3:13 note: ...but borrowed value is only valid for the statement at 3:4
Apparently, &31i goes out of scope if it's assigned to y after y has been declared. However, if it is on the same line where y is declared it stays in scope. I don't see the reason why this is.
What about Rust's design makes it behave this way? Thanks in advance.
I think this happens because of different rules for & operator when it is used in bindings or elsewhere.
This:
let y: &int = &31i;
is equivalent to this:
let temp: int = 31i;
let y: &int = &temp;
except that temp is invisible. This is explained e.g. in lifetimes guide, though this guide seems to be the older version which has not been rewritten yet (like other guides).
But this:
let y: ∫
y = &31i;
does not have such semantics for some reason, so 31i lives only inside its expression (i.e. 31i). Consequently, you can't take a reference to it because it is discarded immediately.
I'd argue that this is somewhat counterintuitive and probably worth creating an issue. Maybe it is just one of those things which were overlooked because of more important things.