Does the third rule of lifetime elision capture all cases for struct implementations?
Asked Answered
R

2

8

The third rule of lifetime elision says

If there are multiple input lifetime parameters, but one of them is &self or &mut self because this is a method, then the lifetime of self is assigned to all output lifetime parameters. This makes writing methods much nicer.

Here is the tutorial describing what happened for this function

fn announce_and_return_part(&self, announcement: &str) -> &str

There are two input lifetimes, so Rust applies the first lifetime elision rule and gives both &self and announcement their own lifetimes. Then, because one of the parameters is &self, the return type gets the lifetime of &self, and all lifetimes have been accounted for.

We can show that all the lifetimes are not accounted for since it is possible that announcement will have a different lifetime than &self:

struct ImportantExcerpt<'a> {
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        announcement
    }
}

fn main() {
    let i = ImportantExcerpt { part: "IAOJSDI" };
    let test_string_lifetime;

    {
        let a = String::from("xyz");
        test_string_lifetime = i.announce_and_return_part(a.as_str());
    }
    println!("{:?}", test_string_lifetime);   
}

The lifetime of announcement is not as long as &self, so it is not correct to associate the output lifetime to &self, shouldn't the output lifetime be associated to the longer of the input?

Why is the third rule of lifetime elision a valid way to assign output lifetime?

Recidivate answered 20/9, 2017 at 23:36 Comment(2)
I totally thought the same thing as you when I read that. I guess the important thing to know along with the response below is that the code you posted fails to compile, complaining of a lifetime mismatch between the announcement argument and the return type in the function prototype, explicitly noticing that you're returning data from announcement. Thanks for posting this.Hanshaw
By the way, for future Googlers: If you add an explicit (and matching) lifetime to announcement and the return type in the prototype, the function compiles, but then rust (correctly) complains about the lifetime not being long enough in main().Hanshaw
T
8

No, the elision rules do not capture every possible case for lifetimes. If they did, then there wouldn't be any elision rules, they would be the only rules and we wouldn't need any syntax to specify explicit lifetimes.

Quoting from the documentation you linked to, emphasis mine:

The patterns programmed into Rust's analysis of references are called the lifetime elision rules. These aren't rules for programmers to follow; the rules are a set of particular cases that the compiler will consider, and if your code fits these cases, you don't need to write the lifetimes explicitly.

The elision rules don't provide full inference: if Rust deterministically applies the rules but there's still ambiguity as to what lifetimes the references have, it won't guess what the lifetime of the remaining references should be. In this case, the compiler will give you an error that can be resolved by adding the lifetime annotations that correspond to your intentions for how the references relate to each other.


The lifetime of announcement is not as long as &self, so it is not correct to associate the output lifetime to &self

Why is the third rule of lifetime elision a valid way to assign output lifetime?

"correct" is probably not the right word to use here. What the elision rules have done is a valid way, it just doesn't happen to be what you might have wanted.

shouldn't the output lifetime be associated to the longer of the input?

Yes, that would be acceptable for this example, it's just not the most common case, so it's not what the elision rules were aimed to do.

See also:

Thurmond answered 20/9, 2017 at 23:41 Comment(5)
but in the doc it says Then, because one of the parameters is &self, the return type gets the lifetime of &self, and all lifetimes have been accounted for. , but it seems like all the lifetime has not been accounted for, how does the compiler know that assigning &self lifetime is the valid one?Recidivate
@Recidivate yes, all the arguments / return types that require lifetimes have been assigned lifetimes, but that doesn't mean that the compiler-assigned lifetimes are the most precise or what you need. Perhaps the issue is just centered on the semantics around the phrase "accounted for"?Thurmond
ah, I see, the compiler just assign the lifetime to the most general case, which maybe a case that is not correct.Recidivate
@Jal: I'd like to add that the compiler shouldn't have to look at the implementation of a function. Lifetime elision rules are only concerned about the function signature. If it matches a certain pattern, lifetimes are assigned automatically. fn(&self, &str) -> &str is always short for fn<'a>(&'a self, &str) -> &'a str regardless of how you implement that function. In your case, this matching pattern is not what you want which is why you have to be specific about the lifetimes.Obtund
it won't guess what the lifetime of the remaining references should be. Certainly seems like the compiler is guessing here. When I read that part of the book - even those exact quotes - I thought the same thing: the compiler only uses elision rules in common cases, where there is only one possible way to set explicit lifetimes anyway. Apparently not. The book could use some better wording to clarify this.Ciccia
O
0

To add a bit of extra detail to Philippe Chaintreuil's great comment, the elision rules end up making the method signature look like this:

fn announce_and_return_part<'a, 'b>(&'a self, announcement: &'b str) -> &'a str {

Notice that the first elision rules assigns the 'b lifetime to the announcement parameter, and the third elision rules assigns the 'a lifetime to the return value.

I think the reason the compiler notices the lifetime mismatch is that when the method returns announcement, the compiler says, "wait a second, announcement's type is &'b str, but this method is supposed to return a value of type &'a str. Something is wrong!"

In other words, both the type and the lifetime of the value returned must match the method signature. Otherwise, the code won't compile. That's how Rust can tell if the assumptions made by the elision rules actually aren't true for the case at hand.

Ostium answered 20/7 at 6:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.