Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problems with lifetime/borrow on str type

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
like image 424
Vaillant Etienne Avatar asked Dec 23 '22 18:12

Vaillant Etienne


2 Answers

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)

like image 186
SCappella Avatar answered Dec 26 '22 01:12

SCappella


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.

like image 29
Peter Hall Avatar answered Dec 26 '22 02:12

Peter Hall