Rust book and lifetimes

After some programming I found that it was time to re-read the rust book, and stumbled across the following section:

https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html

Here, it says:

The third rule is that, if there are multiple input lifetime parameters, but one of them is &self or &mut self because this is a method, the lifetime of self is assigned to all output lifetime parameters. This third rule makes methods much nicer to read and write because fewer symbols are necessary.

The example provided a bit below is:

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

In this case I see the reasoning, but I can't see how this "third rule" can apply when instead of self.part a reference to something else, for example announcement, would be returned.
I don't want to be nitpicking, but shouldn't the rule include "... if a reference to self/a field of self/...." is returned? Or did I miss something else here?

That is from the "Lifetime Elision" section. See also in that section:

if your code fits these cases, you don’t need to write the lifetimes explicitly

Since it doesn't fit the elision cases, you would need to write the lifetimes explicitly.

The book says this:

When annotating lifetimes in functions, the annotations go in the function signature, not in the function body. The lifetime annotations become part of the contract of the function, much like the types in the signature. Having function signatures contain the lifetime contract means the analysis the Rust compiler does can be simpler. If there’s a problem with the way a function is annotated or the way it is called, the compiler errors can point to the part of our code and the constraints more precisely. If, instead, the Rust compiler made more inferences about what we intended the relationships of the lifetimes to be, the compiler might only be able to point to a use of our code many steps away from the cause of the problem.

5 Likes

These rules tell Rust what you "meant" if you don't write out all lifetimes explicitly - and if Rust gets it wrong, you can write out all lifetimes yourself.

So, rule three means that that example is expanded for later stages of compilation to:

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part<'this, 'announce>(&'this self, announcement: &'announce str) -> &'this str {
        …
    }
}

because this is how the elision rules interpret that example. Now, it's possible that what you wanted Rust to come up with was:

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part<'this, 'announce>(&'this self, announcement: &'announce str) -> &'announce str {
        …
    }
}

If this is the case, then you need to use explicit lifetimes to guide Rust into coming up with the right thing:

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part<'announce>(&self, announcement: &'announce str) -> &'announce str {
        …
    }
}

The elision rules still apply to &self here, but Rust will invent a new lifetime there for you because of the first rule, and come up with the final version that matches what you want.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.