Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Don’t hide lifetimes

When you use lifetime-carrying structs (whether your own or someone else’s), the Rust compiler sometimes lets you elide the lifetime parameter when mentioning the struct:

#![allow(unused)]
fn main() {
struct Foo<'a>(&'a str);

impl<'a> Foo<'a> {
    // No lifetime syntax needed :-(
    //                 vvv
    fn new(s: &str) -> Foo {
        Foo(s)
    }
}
}

This can make it non-obvious that borrowing is going on, and harder to figure out where errors are coming from. The above code used to be silently accepted, but there are now some lints, like mismatched_lifetime_syntaxes, which fire for the most common troublesome patterns. To save yourself some headaches, you may even want to make the warnings into errors:

#![allow(unused)]
#![deny(mismatched_lifetime_syntaxes)]
fn main() {
struct Foo<'a>(&'a str);

impl<'a> Foo<'a> {
    // Now this is an error
    //                 vvv
    fn new(s: &str) -> Foo {
        Foo(s)
    }
}
}

There is also an allow-by-default lint called elided_lifetimes_in_paths which fires on code patterns considered less likely to be problematic.

The first thing I do when taking on a borrow check error in someone else’s code is to check these lints. If you have not enabled the lints and are getting errors in your own code, try enabling the lints. For every place that errors, take a moment to pause and consider what is going on with the lifetimes. Sometimes there’s only one possibility and you will just need to make a trivial change to appease the lint:

-    fn new(s: &str) -> Foo {
+    fn new(s: &str) -> Foo<'_> {

But often, in my experience, one of the error sites will be part of the problem you’re dealing with. (This may become less common in the future now that mismatched_lifetime_syntaxes is a warning by default.)

Be aware of impl Trait and async fn capturing

When you see an async fn or a -> impl function which has lifetimes in the arguments, such as these:

fn example(s: &str) -> impl Iterator<Item = Result<String, io::Error>> { ... }

async fn example(v: &mut Vec<String>) -> String { ... }

The return type will by default contain all lifetimes from the inputs, even though there is no '_ or & to indicate it. (They will also effectively contain any type generics, which may themselves contain lifetimes.)

So think of -> impl and async fn as a sign that there may be a borrowing relationship present between the inputs and outputs, similarly to what -> &_ and -> Foo<'_> indicate.

See this section for more details on how the borrowing relationships work and how you can refine them.