2

I've started to skim through some Rust tutorials and finally stumbled upon lifetimes. I can see the need for them in those examples defining multiple scopes, but I can't wrap my head around why they are required in the function longest in

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}

The way I see it, the compiler knows that the lifetime of both strings goes until the end of the program and neither go out of scope before. So, why do we still need to specify the lifetimes explicitly?

I have the feeling that I'm missing something obvious here but I couldn't find a satisfactory explanation.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
zeawoas
  • 446
  • 7
  • 18
  • 1
    The purpose is to allow the compiler to know the lifetime of the **result** of the function (given the lifetimes of the inputs) – donkopotamus Mar 04 '20 at 22:14
  • yeah but since *both* inputs live until the end of the program, can't the compiler infer that the result would do so too? – zeawoas Mar 04 '20 at 22:14
  • *the compiler knows that the lifetime of both strings goes until the end of the program and neither go out of scope before* -- actually, that's not correct: `string1` *does* go out of scope before program termination, and its destructor *will* be called, so `result` is not of type `&'static str` like it could be if `string1` and `string2` were both string literals; it is of some other type `&'a str` where `'a` is something smaller than `'static`. – trent Mar 04 '20 at 22:15
  • 6
    The function doesn’t know that it’ll be called with long-lived values. It has to compile on its own. – Ry- Mar 04 '20 at 22:15
  • *since both inputs live until the end of the program, can't the compiler infer that the result would do so too?* -- yes, it can do that *because* you wrote that lifetime relationship explicitly when you defined `longest`. `fn longest(x: &str, y: &str) -> &'static str` (for example) would be a different function with a different lifetime contract. – trent Mar 04 '20 at 22:17
  • 1
    I think the answers to this question should answer yours: [Why are explicit lifetimes needed in Rust?](https://stackoverflow.com/questions/31609137/why-are-explicit-lifetimes-needed-in-rust) What do you think? – trent Mar 04 '20 at 22:18
  • @Ry The compiler does not need lifetime annotations for the function to compile. The annotations are only needed at the call site. – Sven Marnach Mar 04 '20 at 22:18
  • @trentcl *actually, that's not correct: string1 does go out of scope before program termination* -- that's a good point. haven't thought about that. thanks – zeawoas Mar 04 '20 at 22:18
  • @SvenMarnach: [No?](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=20bfacc2cac8ad3dbd6e927d09e95ad0) – Ry- Mar 04 '20 at 22:19
  • @trentcl I have seen this question before, but the answers either use examples with separate scopes or say *It's impossible to tell without inspecting the source, which would go against a huge number of coding best practices.* -- I can see that it violates best practices, however, the compiler could still infer that the above example should be safe without explicit lifetimes. – zeawoas Mar 04 '20 at 22:21
  • @Ry This misses the point I was trying to make, but I guess the point is far to complex to fit into a useful SO comment. – Sven Marnach Mar 04 '20 at 22:22
  • @SvenMarnach: Okay but when I say that it has to compile on its own I mean that it can’t rely on how it’s called to determine whether it itself is valid, not the implementation details of code generation. (I think you’re missing the point I was trying to make.) – Ry- Mar 04 '20 at 22:22
  • The annotations are not needed for code generation. They are needed for the borrow checker, but in fact the borrow checker also uses them to check the function itself. – Sven Marnach Mar 04 '20 at 22:23
  • I fully understand what you are trying to say, but I don't think it's the crux of the issue. It would be perfectly possible for the Rust compiler to figure out the lifetime annotations based on the function body. It doesn't need the annotation at that point. – Sven Marnach Mar 04 '20 at 22:24
  • 1
    @zeawoas I'm going to ask you to be more clear: what do you mean *the compiler could infer*? The compiler *does* infer the lifetimes, *in `main`*. Only `longest` requires lifetime annotations, and the reason for that is as Shepmaster's answer to the other question: *Functions are a natural boundary to firewall the effects of changing code.* – trent Mar 04 '20 at 22:25
  • @SvenMarnach: Oh, that makes sense, but I was explaining why “the compiler knows that the lifetime of both strings goes until the end of the program and neither go out of scope before” from the question doesn’t work, not trying to say that the correct relationships couldn’t be inferred from the function body. – Ry- Mar 04 '20 at 22:26
  • I think the point I was trying to make is best captured [in this answer](https://stackoverflow.com/a/31612025). – Sven Marnach Mar 04 '20 at 22:29
  • 1
    [Here's an example of where the lifetimes on `longest` come into play, maybe looking at it will help clarify your question](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f1ad905f3238efc6cbcf9023c421c744). – trent Mar 04 '20 at 22:32
  • @trentcl What I meant is what Shepmaster calls "lifetimes to be completely inspected from the code". I thought the reason why this is not done is because it is impossible but apparently it was a design choice. Also thanks for the example, but again it uses an inner scope for which I do understand the need for explicit lifetimes. I guess I was just confused that you need to specify lifetimes for variables the scope of which spans all of `main`. Maybe they'll implement another elision rule giving the returned variable the shortest lifetime among the function arguments sometime in the future. – zeawoas Mar 04 '20 at 22:45
  • In your example (and in the examples in my playground link), you don't specify lifetimes for anything in `main`. The compiler figures those out. You only specify lifetimes for things in `longest`, which has to be valid anywhere `longest` could be called from, not only in `main`. – trent Mar 04 '20 at 22:56
  • 1
    I understand that. Sorry for wording imprecisely. However, the compiler knows that in these examples `longest` is only called in `main` and hence that there won't be a 'use after free'. Anyway, I think we are talking past each other at this point. I just expected the compiler to resolve such situations by code inspection, which it does not - not because it's not possible, but because the Rust developers chose so. Thanks for your effort, though, I'm going to upvote some of your comments. – zeawoas Mar 04 '20 at 23:21
  • 1
    *I just expected the compiler to resolve such situations by code inspection, which it does not - not because it's not possible, but because the Rust developers chose so.* — that is the point of the answer on [Why are explicit lifetimes needed in Rust?](https://stackoverflow.com/q/31609137/155423) – Shepmaster Mar 05 '20 at 00:53

0 Answers0