The core concept to learn here is one called a "place expression", as opposed to a "value expression". In other systems languages like C/C++, these have names like "l*value" vs "rvalue". [The rationale of this naming is that in an assignment expr1 = expr2
, the left-hand side is an "l value" and the right-hand side is an "r value".] Place expressions in Rust don't refer to a value that is first created by evaluating the expression, instead they refer to a value that (generally) already existed somewhere previously.
As such, place expressions can come with ownership restrictions. You can move out of a place, but not all places support moving. You can borrow a place, but that can also be restricted, e.g. some places might only allow borrowing immutably, etc…
The easiest example of a place expression is the name of a variable in scope.
If you have let x = 1;
and then write let y = x;
then you’re reading (moving; or in this case copying) the value from the place x
; but the place x
also supports borrowing; e.g. if you write &mut x
, that borrows more than just "the value 1
", it borrows the value 1 in its original place, and mutations affect the original variable x
.
A common example of a places that don't support moving is places (with non-Copy
type) behind a reference.
let mut x = Vec::<u8>::new();
let r = &mut x;
// consider the expression `*r` of type `Vec<u8>`
// let v = *r; // doesn't work
// --> error[E0507]: cannot move out of `*r` which is behind a mutable reference
// but taking a reference of *that* place, e.g. `& *r`, of type `&Vec<u8>` is okay
let r2: &Vec<u8> = & *r;
maybe you're not too familiar with cases like &*r
yet, but dereferencing is often implicit, e.g. with a field access, so a more common example would be something like
struct MyStruct {
field: Vec<u8>,
}
impl MyStruct {
fn add_value(&mut self, val: u8) {
// in `self`, we have `self.field` of type `Vec<u8>`
// we can't move its value:
// let field_value = self.field; // doesn't work
// --> error[E0507]: cannot move out of `self.field` which is behind a mutable reference
// but we can call the `push` method
self.field.push(val);
}
}
this does however desugar to something similar, in this case self.field.push(val)
desugars to something like
Vec::<u8>::push(&mut (*self).field, val);
where self
is dereferenced, then the field is accessed, a reference to that field is created, and finally push
is called. This also shows some further operations place expressions do support, that is: you can access a field of a place (which then also is a place, which inherits many restrictions such as whether or not moves, or certain types of borrows are allowed).
And indeed method calls are also possible on places, and often times don't involve any moving of the value, but only borrowing. This relates to automatic referencing, as @nerditation above explained, though automatic referencing alone doesn't really answer your question, since (partially) desugaring
v[..].starts_with(&[1,2])
into something like
(&v[..]).starts_with(&[1,2])
would't explain why a = v[..]
is not allowed but &v[..]
is allowed.
Finally, this example also involve "unsized" (aka dynamically sized) types. However, that detail isn’t the most relevant, because it’s just yet-another reason why certain places can't support moving the value, but that kind of restriction is in no way unique to "unsized" types.
For further reading, I’d be happy if someone finds more beginner-friendly material; I can find the section in the reference which is however terse and technical; and e.g. this blog post which focuses on certain aspects and technicalities only, but at least is more meant to be read by humans.