Coming from C++, I'm rather surprised that this code is valid in Rust:
let x = &mut String::new();
x.push_str("Hello!");
In C++, you can't take the address of a temporary, and a temporary won't outlive the expression it appears in.
How long does the temporary live in Rust? And since x
is only a borrow, who is the owner of the string?
Why is it legal to borrow a temporary?
It's legal for the same reason it's illegal in C++ — because someone said that's how it should be.
How long does the temporary live in Rust? And since
x
is only a borrow, who is the owner of the string?
The reference says:
the temporary scope of an expression is the smallest scope that contains the expression and is for one of the following:
- The entire function body.
- A statement.
- The body of a
if
,while
orloop
expression.- The
else
block of anif
expression.- The condition expression of an
if
orwhile
expression, or amatch
guard.- The expression for a match arm.
- The second operand of a lazy boolean expression.
Essentially, you can treat your code as:
let mut a_variable_you_cant_see = String::new();
let x = &mut a_variable_you_cant_see;
x.push_str("Hello!");
See also:
From the Rust Reference:
Temporary lifetimes
When using a value expression in most place expression contexts, a temporary unnamed memory location is created initialized to that value and the expression evaluates to that location instead
This applies, because String::new()
is a value expression and being just below &mut
it is in a place expression context. Now the reference operator only has to pass through this temporary memory location, so it becomes the value of the whole right side (including the &mut
).
When a temporary value expression is being created that is assigned into a let declaration, however, the temporary is created with the lifetime of the enclosing block instead
Since it is assigned to the variable it gets a lifetime until the end of the enclosing block.
This also answers this question about the difference between
let a = &String::from("abcdefg"); // ok!
and
let a = String::from("abcdefg").as_str(); // compile error
In the second variant the temporary is passed into as_str()
, so its lifetime ends at the end of the statement.
Rust's MIR provides some insight on the nature of temporaries; consider the following simplified case:
fn main() {
let foo = &String::new();
}
and the MIR it produces (standard comments replaced with mine):
fn main() -> () {
let mut _0: ();
scope 1 {
let _1: &std::string::String; // the reference is declared
}
scope 2 {
}
let mut _2: std::string::String; // the owner is declared
bb0: {
StorageLive(_1); // the reference becomes applicable
StorageLive(_2); // the owner becomes applicable
_2 = const std::string::String::new() -> bb1; // the owner gets a value; go to basic block 1
}
bb1: {
_1 = &_2; // the reference now points to the owner
_0 = ();
StorageDead(_1); // the reference is no longer applicable
drop(_2) -> bb2; // the owner's value is dropped; go to basic block 2
}
bb2: {
StorageDead(_2); // the owner is no longer applicable
return;
}
}
You can see that an "invisible" owner receives a value before a reference is assigned to it and that the reference is dropped before the owner, as expected.
What I'm not sure about is why there is a seemingly useless scope 2
and why the owner is not put inside any scope; I'm suspecting that MIR just isn't 100% ready yet.
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