Why is the assignment in the following snippet allowed?
#[derive(Copy, Clone)]
pub struct X {
pub a: u8,
}
fn main() {
let x = X { a: 0 };
{ x }.a = 5;
assert!(x.a == 5); // I was wrong
}
Am I assigning to a temporary?
Although these are forbidden:
({ x.a }) = 5;
let mut y: u8 = 0;
({ y }) = 5;
I ask, because I had a bug like
unsafe { *some_struct }.mem1 = 5;
I didn't use
unsafe { *some_struct.mem1 = 5; }
from the beginning, because it felt right to keep unsafe blocks as short as possible.
Yes, you are assigning to a temporary, because X implement Copy, so doing { x }
you are creating a block that return x, but X implement Copy, so your block return a copy of x. Your block is basicly this:
#[derive(Copy, Clone)]
pub struct X {
pub a: u8,
}
fn main() {
let x = X { a: 0 };
let mut tmp = { x };
tmp.a = 5;
assert_eq(x.a, 0);
assert_eq!(tmp.a, 5);
}
What you can do to make the block return the actual variable instead of the copy is to transform the pointer to a mutable reference, and return that ref from the unsafe block:
#[derive(Copy, Clone)]
pub struct X {
pub a: u8,
}
fn main() {
let mut x = X { a: 0 };
let ptr: *mut X = &mut x;
unsafe {
&mut *ptr
}.a = 5;
assert_eq!(x.a, 5);
}
Why is it allowed? It is allowed because Rust lets you modify temporaries, because a lot of things would be very verbose otherwise, like:
fn get_first<T>(vec: Vec<T>) -> Option<T> {
let first = vec.into_iter().next();
// ^ temporary iterator
first
}
next()
needs a mutable ref to the iterator. Having to bind it to a mutable variable would mean that you would have to write:
fn get_first<T>(vec: Vec<T>) -> Option<T> {
let mut iter = vec.into_iter();
let first = iter.next();
first
}
The snippets of forbidden code that you provided don't satisfy the compiler because you try to assign a value to another value, not to a variable: doing { var }
creates a block that return the value in the variable, so:
({ x.a }) = 5;
// ^ block return the value in x.a
let mut y: u8 = 0;
({ y }) = 5;
// ^ same here, return the value in y
You can consume this temporary, but not overwrite it, this is intentional and a good thing. I think in C++ they call this lvalue and rvalue, the block return a rvalue, and you can't assign to a rvalue.
But yes the compiler don't see { x }.a
as a temporary, which is a bit silly, there is no use case that I can think of, and can create confusion.
I tried to simplify as much as possible the initial example.
No more Copy
or Clone
are involved; only temporaries are explicitly created.
The first attempt with 1
and 11
is quite normal; nothing more to be said.
The second attempt with 2
is totally useless since the created temporary disappears right after its creation (no side effect, this will probably be
optimised out).
The third attempt with 3
and 33
is even more useless than the previous one since we alter the temporary just before it disappears (no more side effect, this will probably be optimised out).
The fourth attempt with 4
and 44
directly relates to the question.
Although it is as useless as the two previous attempts, it is rejected by the compiler.
The error code is E0070
and rustc --explain E0070
says:
The left-hand side of an assignment operator must be a place expression. A place expression represents a memory location and can be a variable (with optional namespacing), a dereference, an indexing expression or a field reference.
The section Place Expressions and Value Expressions of The Rust Reference says something similar with more details.
Thus, the raw answer about the difference between the third and fourth attempts simply stands in the fact that the field reference of the third attempt is considered as a place expression.
However, I don't find this answer very satisfying since I don't know the motivation of the language designers in this choice. Why do they consider a temporary as a value expression but consider a field of this temporary as a place expression?
struct X {
a: u8,
}
fn main() {
let mut _x = X { a: 1 };
_x = X { a: 11 };
X { a: 2 }; // created, then dropped straight away
X { a: 3 }.a = 33; // created, altered, then dropped straight away
// X { a: 4 } = X { a: 44 }; // fails with error E0070
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With