Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this closure's lifetime change based on seemingly unrelated types?

Tags:

rust

With the code

fn foo<'a, 'b>(
    state: &'b mut i32,
) -> impl FnMut(&'a str) -> &'static str + 'b {
    |s| "hi"
}

I get an error

error[E0482]: lifetime of return value does not outlive the function call
112 | ) -> impl FnMut(&'a str) -> &'static str + 'b {
    |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: the return value is only valid for the lifetime `'a` as defined on the function body at 110:9
110 | fn foo1<'a, 'b>(
    |         ^^

But somehow the code

fn foo2<'a, 'b>(
    state: &'b mut Option<&'a i32>,
) -> impl FnMut(&'a str) -> &'static str + 'b {
    |s| "hi"
}

compiles without errors. Why does the type of state change the lifetime of the closure?

like image 798
Dan Avatar asked Mar 02 '23 10:03

Dan


1 Answers

With type &'b mut Option<&'a i32>, Rust infers the lifetime bound 'a: 'b ('a outlives 'b). This bound is required for the function signature to be well-formed. You can add this bound explicitly to avoid the error:

fn foo<'a, 'b>(
    state: &'b mut i32,
) -> impl FnMut(&'a str) -> &'static str + 'b
where
    'a: 'b,
{
    |s| "hi"
}

However, if no parameter uses 'a, then 'a should be a higher-rank trait bound instead:

fn foo<'b>(
    state: &'b mut i32,
) -> impl for<'a> FnMut(&'a str) -> &'static str + 'b {
    |s| "hi"
}

The Fn family of traits are special in that they allow you to omit the lifetime entirely (following the same elision rules as fn signatures), so this is equivalent:

fn foo<'b>(
    state: &'b mut i32,
) -> impl FnMut(&str) -> &'static str + 'b {
    |s| "hi"
}
like image 53
Francis Gagné Avatar answered May 31 '23 08:05

Francis Gagné