Reputation: 185
I'm reading Rust Programming Language book and would like to clarify following Listing 4.5 in Return Values and Scope section:
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len() returns the length of a String
(s, length)
}
If I use:
fn calculate_length(s: String) -> (String, usize) {
(s, s.len())
}
then I'm getting:
error[E0382]: borrow of moved value: `s`
--> guessing_game/src/main.rs:46:9
|
43 | fn calculate_length(s: String) -> (String, usize) {
| - move occurs because `s` has type `String`, which does not implement the `Copy` trait
...
46 | (s, s.len())
| - ^ value borrowed here after move
| |
| value moved here
At the same time if I swap values in returning tuple to (s.len(), s)
(in calculate_length
function) and rewrite main
caller accordingly, it compiles without errors.
Intuitively I understand this borrowing error (and how to fix it), but could somebody clarify/explain this behavior "more formally"?
Upvotes: 0
Views: 150
Reputation: 43743
since this construction seems to be an "atomic" operation
Constructing a tuple is “atomic”, but evaluating an expression is not. In general, expressions can contain arbitrary side effects in any part of them, so the order of evaluation of sub-expressions can affect the semantics of the program. Some languages leave it partly unspecified, but in Rust, the evaluation order is always left-to-right.
So, evaluating (s, s.len())
consists of the following steps:
s
. Since s
is a type that is not Copy
, this moves the value out of the variable s
.s.len()
. (This is an error since s
is already moved.)In this case, looking at the program we can see that it would not have any undesirable consequences to reorder the two subexpression evaluations, but Rust's evaluation order does not have any special cases for different types of expressions.
(The optimizer will perform lots of transformations on this code, in particular making nearly all “moves” not involve any memory copying or other actual CPU operations, but it would be very hard to program Rust if the validity of a program depended on the optimizer's choices.)
Upvotes: 2