Search code examples
rustclosureslifetime

Why doesn't lifetime elision work for a function that returns a Fn?


According to The Rust Book, lifetime elision allows we to have this signature:

fn first_word(s: &str) -> &str {
...
}

Instead of this more verbose version:

fn first_word<'a>(s: &'a str) -> &'a str {
...
}

But later in the same book, I found that I can't write this:

fn make_a_cloner(s: &str) -> impl Fn() -> String {
  move || s.to_string()
}

I have to give the return type a lifetime:

fn make_a_cloner(s: &str) -> impl Fn() -> String + '_ {
...
}

I'm a bit confused. Why don't we need to specify any lifetime when we return a &str, but we need to add '_ when we return a impl Fn() -> String? Why doesn't the same elision rule apply in both cases?


Solution

  • As much as lifetime elision can be obscuring, Rust's syntax does want it to be clear when borrows occur. References are one such place that is a clear borrower and even for structs with lifetimes you are encouraged to use MyStruct<'_> for arguments and parameters so that the lifetime is obvious. There is a elided_lifetimes_in_paths lint in the compiler to help with this, but is unfortunately allowed by default; I encourage you to add it in your own code.

    However, when you return a impl Trait (with no lifetime) then there is no annotation to convey that a borrow has occurred. Adding the placeholder lifetime + '_ at least communicates to the compiler and other developers that a borrow is happening. As such, without a lifetime annotation returning a impl Trait will use 'static by default unless there is some other part of the trait's type that conveys a borrow (i.e. impl Trait<&'a T> will be bound by 'a, not 'static).

    This does not necessarily apply to impl Trait in other places (like function parameters).