Why does this code compile?
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {
let x = "eee";
let &m;
{
let y = "tttt";
m = longest(&x, &y);
}
println!("ahahah: {}", m);
}
For me, there should be a compilation error because of lifetimes.
If I write the same code with i64
, I get an error.
fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
if x > y {
x
} else {
y
}
}
fn main() {
let x = 3;
let &m;
{
let y = 5;
m = ooo(&x, &y);
}
println!("ahahah: {}", m);
}
The error is:
error[E0597]: `y` does not live long enough
--> src/main.rs:103:25
|
103 | m = ooo(&x, &y);
| ^^ borrowed value does not live long enough
104 | }
| - `y` dropped here while still borrowed
105 | println!("ahahah: {}", m);
| - borrow later used here
There are a few things we need to know to understand this. The first is what the type of a string literal is. Any string literal (like "foo"
) has the type &'static str
. This is a reference to a string slice, but moreover, it's a static reference. This kind of reference lasts for the entire length of the program and can be coerced to any other lifetime as needed.
This means that in your first piece of code, x and y are already both references and have type &'static str
. The reason the call longest(&x, &y)
still works (even though &x
and &y
have type &&'static str
) is due to Deref coercion. longest(&x, &y)
is really de-sugared as longest(&*x, &*y)
to make the types match.
Let's analyze the lifetimes in the first piece of code.
fn main() {
// x: &'static str
let x = "eee";
// Using let patterns in a forward declaration doesn't really make sense
// It's used for things like
// let (x, y) = fn_that_returns_tuple();
// or
// let &x = fn_that_returns_reference();
// Here, it's the same as just `let m;`.
let &m;
{
// y: &'static str
let y = "tttt";
// This is the same as `longest(x, y)` due to autoderef
// m: &'static str
m = longest(&x, &y);
}
// `m: &static str`, so it's still valid here
println!("ahahah: {}", m);
}
(playground)
With the let &m;
you may have meant something like let m: &str
to force its type. This I think actually would ensure that the lifetime of the reference in m
starts with that forward declaration. But since m
has type &'static str
anyway, it doesn't matter.
Now let's look at the second version with i64
.
fn main() {
// x: i64
// This is a local variable
// and will be dropped at the end of `main`.
let x = 3;
// Again, this doesn't really make sense.
let &m;
// If we change it to `let m: &i64`, the error changes,
// which I'll discuss below.
{
// Call the lifetime roughly corresponding to this block `'block`.
// y: i64
// This is a local variable,
// and will be dropped at the end of the block.
let y = 5;
// Since `y` is local, the lifetime of the reference here
// can't be longer than this block.
// &y: &'block i64
// m: &'block i64
m = ooo(&x, &y);
} // Now the lifetime `'block` is over.
// So `m` has a lifetime that's over
// so we get an error here.
println!("ahahah: {}", m);
}
(playground)
If we change the declaration of m
to let m: &i64
(which is what I think you meant), the error changes.
error[E0597]: `y` does not live long enough
--> src/main.rs:26:21
|
26 | m = ooo(&x, &y);
| ^^ borrowed value does not live long enough
27 | } // Now the lifetime `'block` is over.
| - `y` dropped here while still borrowed
...
30 | println!("ahahah: {}", m);
| - borrow later used here
(playground)
So now we explicitly want m
to last as long as the outer block, but we can't make y
last that long, so the error happens at the call to ooo
.
Since both these programs are dealing with literals, we actually can make the second version compile. To do this, we have to take advantage of static promotion. A good summary of what that means can be found at the Rust 1.21 announcement (which was the release the introduced this) or at this question.
In short, if we directly take a reference to a literal value, that reference may be promoted to a static reference. That is, it's no longer referencing a local variable.
fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
if x > y {
x
} else {
y
}
}
fn main() {
// due to promotion
// x: &'static i64
let x = &3;
let m;
{
// due to promotion
// y: &'static i64
let y = &5;
// m: &'static i64
m = ooo(x, y);
}
// So `m`'s lifetime is still active
println!("ahahah: {}", m);
}
(playground)
Your examples are not exactly the same. A string literal, "eee"
has type &str
, not str
, so the corresponding code with integers looks like this:
fn ooo<'a>(x: &'a i64, y: &'a i64) -> &'a i64 {
if x > y {
x
} else {
y
}
}
fn main() {
let x = &3;
let &m;
{
let y = &5;
m = ooo(&x, &y);
}
println!("ahahah: {}", m);
}
And it compiles.
The reason that this (and the &str
example) works is because local inline literal references are given the 'static
lifetime. That is, they are placed in the static data portion of the final binary and are available in memory for the full lifespan of the program.
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