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 lifetime of temporary values is typically
- the innermost enclosing statement; the tail expression of a block is
considered part of the statement that encloses the block, or
- the
condition expression or the loop conditional expression if the
temporary is created in the condition expression of an
if
or an
if
/else
or in the loop
conditional expression of a while expression.
When a temporary rvalue is being created that is assigned into a let
declaration, however, the temporary is created with the lifetime of
the enclosing block instead, as using the enclosing statement (the let
declaration) would be a guaranteed error (since a pointer to the
temporary would be stored into a variable, but the temporary would be
freed before the variable could be used). The compiler uses simple
syntactic rules to decide which values are being assigned into a let
binding, and therefore deserve a longer temporary lifetime.
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:
- What is the scope of unnamed values?
- Are raw pointers to temporaries ok in Rust?
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.
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.