First of all, the following code is correct:
fn main() {
let a = &get_i32();
println!("{}", a);
}
fn get_i32() -> i32 {
return 100;
}
But following code get error:
fn main() {
let a;
a = &get_i32();
println!("{}", a);
}
error[E0716]: temporary value dropped while borrowed
--> src/bin/rust_course.rs:8:10
|
8 | a = &get_i32();
| ^^^^^^^^^- temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
9 | println!("{}", a);
| - borrow later used here
|
help: consider using a `let` binding to create a longer lived value
|
8 ~ let binding = get_i32();
9 ~ a = &binding;
|
For more information about this error, try `rustc --explain E0716`.
What are the essential differences between these two pieces of code?I understand that &get_i32()
always returns a temporary value, so it should always report an error should all.
A similar problem:
fn main() {
let s1 = &String::from("hello world");
println!("{}", s1);
let s2 = String::from("hello world").as_str();
println!("{}", s2);
}
error[E0716]: temporary value dropped while borrowed
--> src/bin/rust_course.rs:6:14
|
6 | let s2 = String::from("hello world").as_str();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
7 | println!("{}", s2);
| -- borrow later used here
|
help: consider using a `let` binding to create a longer lived value
|
6 ~ let binding = String::from("hello world");
7 ~ let s2 = binding.as_str();
|
For more information about this error, try `rustc --explain E0716`.
What's the difference between s1 and s2?
There is a more similar problem:
fn main() {
print(String::from("hello world").as_str());
}
fn print(str: &str) {
println!("{}", str);
}
The above code is correct, but I can't understand why String::from("hello world").as_str()
can be passed to functions but not assigned to variables.
What's the difference between s1 and s2?
The answer is temporary lifetime extension. Sadly this is a somewhat informal process (and as the page notes it's subject to change), but broadly speaking it's that let
used to bind a literal reference (so &something
) can trigger a lifetime extension where something
will get an implicit temporary. So let a = &get_i32();
and let s1 = &String::from("hello world");
benefit from this.
a = &get_i32();
does not, because TLE only works with let
.
let s2 = String::from("hello world").as_str();
also does not, because temporaries get lifetime-extended to the end of a statement, so a chain essentially compiles to a sequence of calls in a block e.g.:
let s2 = {
let _temp = String::from("hello world");
_temp.as_str()
// _temp is dropped here so `as_str` becomes invalid
};
However note that the temporary lives to the end of the statement, in case of
print(String::from("hello world").as_str());
the statement lasts until the end of the print
, in essence:
{
let _temp1 = String::from("hello world");
let _temp2 = _temp1.as_str();
print(_temp2)
};
which is perfectly fine.
This is also why you can write things like:
match &Some(String::new()).as_deref() {
Some("") => println!("ok"),
Some(_) => println!("??"),
None => println!("ko"),
}
The entire match
is a single statement, so the Option<String>
temporary lives until its end, which means we can get references to both the inner and outer values, and work with an &Option<&str>
to a value we never bound anywhere (this is nonsense code but it shows the principle and it's the first thing I thought about).
However there are also cases where it causes issues if e.g. you try to match
on a borrow then move the original value in one of the branch. This has gotten a lot less common with non-lexical lifetimes (and borrow checking) but it still occurs from time to time.