I ran into an issue that forces me to split a nice oneliner into a {}
block with an intermediate let
. The reason for this isn't clear to me at all. I was able to isolate the issue in this minimal example:
struct AB {
a: u8,
b: u8,
}
impl AB {
fn foo(&self) -> String {
String::from("foo")
}
fn bar(self, x: String) -> String {
format!("{} - {} - {}!", x, self.a, self.b)
}
}
fn main() {
let x = AB { a: 3, b: 5 };
let result = x.bar(x.foo());
println!("{}", result);
}
I was under the impression that the arguments to the bar
function would be evaluated before calling bar
. foo
borrows x
during its execution, but when it returns its String
, the borrow is finished, as String
is not a reference bearing x
's lifetime. When bar
gets called, foo
's borrow should be over.
However, the compiler disagrees:
error[E0382]: borrow of moved value: `x`
--> src/main.rs:17:24
|
17 | let result = x.bar(x.foo());
| - ^ value borrowed here after move
| |
| value moved here
|
= note: move occurs because `x` has type `AB`, which does not implement the `Copy` trait
I'm not disagreeing with the fact that bar
indeed moves x
. My issue is with the statement that foo
borrows x
after the move takes place.
A simple (but ugly) fix:
struct AB {
a: u8,
b: u8,
}
impl AB {
fn foo(&self) -> String {
String::from("foo")
}
fn bar(self, x: String) -> String {
format!("{} - {} - {}!", x, self.a, self.b)
}
}
fn main() {
let x = AB { a: 3, b: 5 };
let y = x.foo();
let result = x.bar(y);
println!("{}", result);
}
separating the assignment of x.foo()
to an intermediate variable y
compiles fine, confirming my expectation that the borrow is indeed over once foo
returns, but why does this work? Is there something I don't understand about evaluation order? Why can't I get rid of the intermediate let y
?
To fix it, you can clone the vector. let (a, c) = test(a. clone()); Note that the let (a, c) part does not overwrite the a variable - rather, it declares a new variable also called a , shadowing the old one.
Rust supports a concept, borrowing, where the ownership of a value is transferred temporarily to an entity and then returned to the original owner entity.
Evaluation order, for the purpose of borrows, is from left to right.
This means that the subject of the bar
call, the "move-out" mention of x
, is considered before the "borrow" mention of x
in the foo
call, and therefore, the compiler considers the variable to have been moved from.
For the similar case where the outer mention is a mutable borrow, RFC 2025 has been accepted as a solution, but hasn't been implemented yet. Unfortunately this RFC doesn't appear to cover your case, where the outer use is a move.
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