What's the difference between these function definitions and how to explain the compilation errors about lifetimes?

Snippets derived from here

fn wrap_fn<F>(f: F) -> impl Fn(&str) -> &str where F: Fn(&str) -> &str {    // 1
//fn wrap_fn<F>(f: F) -> F where F: Fn(&str) -> &str {                      // 2
//fn wrap_fn<'b, F>(f: F) -> F where F: Fn(&'b str) -> &'b str {            // 3
    f
}

fn foo(i: &str) -> &str {
    let path: &'static str = "foo";
    wrap_fn(|_| path)(i)      // A
//    wrap_fn(|_| i)(i)       // B
}

fn main() {
    foo("");
}

// A+1: error
// B+1: error
// B+2: error

(Playground)

B2
The function says;

fn wrap_fn<F>(f: F) -> F where F: for<'a> Fn(&'a str) -> &'a str {  // 2

The data type (closure) must work for any lifetime 'a the caller supplies.

let f = wrap_fn(|_| i);
f(&'static "")

i has a limited lifetime and the compiler isn't allowed to go beyond it.


A2 (works)
This time the lifetime for path is 'static. The call with the limited lifetime i gets used. 'static is a subtype of all lifetimes. Variance allows the compiler to return it as the supertype.


A1
Is a longstanding bug in the compiler. The lifetime of the returned closure gets merged with the return value of it getting called.
Split over two lines to make work.

    let f = wrap_fn(|_| path);
    f(i)      // A
2 Likes

It seems like if we don't specify lifetimes in wrap_fn the compiler assumes it's 'static so that the argument and the return Fn matches. In that case, the closure captures the value forever, causes the lifetime error.

fn wrap_fn<F>(f: F) -> impl Fn(&str) -> &str where F: Fn(&str) -> &str {    // 1
//fn wrap_fn<F>(f: F) -> F where F: Fn(&str) -> &str {                      // 2
//fn wrap_fn<'b, F>(f: F) -> F where F: Fn(&'b str) -> &'b str {            // 3
    f
}

/// 1: Er, borrowed value does not live long enough
/// 2: Ok
/// 3: Ok
//fn foo(i: &str) -> &str {
//    let path: &str = "foo";
//    wrap_fn(|_| path)(i)
//}

/// 1: Ok
/// 2: Ok
/// 3: Ok
//fn foo(i: &str) -> &str {
//    let path: &str = "foo";
//    let x = wrap_fn(|_| path)(i);
//    x
//}

/// 1: Er, lifetime of reference outlives lifetime of borrowed content
/// 2: Er, lifetime of reference outlives lifetime of borrowed content
/// 3: Ok
//fn foo(i: &str) -> &str {
//    let path: &str = "foo";
//    wrap_fn(|_| i)(i)
//}

/// 1: Er, lifetime of reference outlives lifetime of borrowed content
/// 2: Er, lifetime of reference outlives lifetime of borrowed content
/// 3: Ok
fn foo(i: &str) -> &str {
    let path: &str = "foo";
    let s;
    {
        let f = wrap_fn(|_| i);
        s = f(path);
    }
    s
}