Can’t declare lifetime for closure that returns a reference

3135f2f
Opened by Yonathan Randolph at 2023-11-26 18:04:16

When you declare closure argument types, there is no syntax to declare a lifetime parameter. And I guess lifetime elision does not apply to closures. Therefore, there seems to be no way to declare the type of a closure that returns a reference.

It compiles if you avoid declaring the type of the closure and depend on type inference. But then you would not be able to assign the closure to a local variable.

fn print_first(list: Vec<String>) {
    let x: &str = list
    .first()
    .map(|s: &String| -> &str &s[])  // ERROR 
    //.map(|s: &String| &s[]) // ERROR
    //.map(|s| -> &str &s[]) // ERROR
    //.map(|s| &s[])  // OK
    .unwrap_or("");
    println!("First element is {}", x);
}

It gives a compiler error and a suggestion that does not make sense.

    src/rusttest.rs:4:29: 4:32 error: cannot infer an appropriate lifetime for lifetime parameter 'a in function call due to conflicting requirements
    src/rusttest.rs:4   .map(|s: &String| -> &str &s[])
                                                   ^~~
    src/rusttest.rs:1:1: 10:2 help: consider using an explicit lifetime parameter as shown: fn print_first<'a>(list: Vec<String>)
    src/rusttest.rs:1 fn print_first(list: Vec<String>) {
    src/rusttest.rs:2   let x: &str = list
    src/rusttest.rs:3   .first()
    src/rusttest.rs:4   .map(|s: &String| -> &str &s[])  // ERROR
    src/rusttest.rs:5   //.map(|s: &String| &s[]) // ERROR
    src/rusttest.rs:6   //.map(|s| -> &str &s[]) // ERROR

This bug is filed after I asked this question on stack overflow. It may be related to Region inference fails for closure parameter #17004.

  1. Triage: there is still no way to declare lifetime parameters for closures like this today.

    Steve Klabnik at 2016-03-04 20:11:40

  2. Lets throw some ideas out there:

    |s: &'a String|<'a> -> &'a str &s[]
    <'a>|s: &'a String| -> &'a str &s[]
    let closure<'a> = |s: &'a String| -> &'a str &s[];
    

    The last one is a little limited, but may actually be feasible unlike the others.

    Diggory Hardy at 2016-05-17 17:34:32

  3. I just ran into this issue today, trying to return a reference out of a closure that had the same lifetime as one of its arguments. Closures with lifetime, as well and type arguments would definitely be nice to have - if we had them, then I'm pretty sure closures would be just as powerful as functions. There's an RFC to implement them both: https://github.com/rust-lang/rfcs/pull/1650

    Michael Hewson at 2017-02-12 21:28:09

  4. I played with a similar example today and found a funny workaround. Just skip type declaration and cast the type in the body of the closure:

    |s| -> &str { &(s as &String)[..] }
    

    or even with #![feature(type_ascription)]

    |s| -> &str { &(s: &String)[..] }
    

    In this way, type inference can do it's job with a lifetime and the type of the argument is limited by the way of use in the body.

    Alexander Irbis at 2017-04-04 22:50:13

  5. There is another problem related to this one:

    error[E0281]: type mismatch: `[closure@src/x.rs:329:50: 335:22 message_type:_]` implements the trait `std::ops::Fn<(_,)>`, but the trait `for<'r> std::ops::Fn<(&'r Message,)>` is required
       --> src/x.rs:341:44
        |
    329 |                       let filter_by_message_type = |x| {
        |  __________________________________________________-
    330 | |                         if (x as &Message).get_type() == *message_type {
    331 | |                             Some(x)
    332 | |                         } else {
    333 | |                             None
    334 | |                         }
    335 | |                     };
        | |_____________________- implements `std::ops::Fn<(_,)>`
    

    which essentiall prevents using closures for for<'r> Fn(..) -> ...bounded generics

    Bernhard Schuster at 2017-10-27 07:24:13

  6. An update: all of the examples given in the bug description do compile today, after being updated for Rust 1.0 syntax:

    fn print_first(list: Vec<String>) {
        let x: &str = list
        .first()
        // .map(|s: &String| -> &str { &s[..] })  // OK
        // .map(|s: &String| { &s[..] }) // OK
        // .map(|s| -> &str { &s[..] }) // OK
        .map(|s| { &s[..] })  // OK
        .unwrap_or("");
        println!("First element is {}", x);
    }
    
    fn main() {
        print_first(vec![format!("hello"), format!("world")]);
    }
    

    Felix S Klock II at 2018-12-06 09:34:10

  7. (However, what I do not yet know is whether the types we are actually inferring in all of the above cases are what the user expects. See related discussion on #56537...)

    Felix S Klock II at 2018-12-06 09:35:26

  8. I've been having a somewhat similar issue being unable to handle lifetimes with closures. Were explicit lifetimes ever added to closures?

    $ rustup --version
    rustup 1.24.1 (a01bd6b0d 2021-04-27)
    info: This is the version for the rustup toolchain manager, not the rustc compiler.
    info: The currently active `rustc` version is `rustc 1.52.1 (9bc8c42bb 2021-05-09)`
    
    $ cargo --version
    cargo 1.52.0 (69767412a 2021-04-21)
    
    $ rustc --version
    rustc 1.52.1 (9bc8c42bb 2021-05-09)
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn closure_lifetimes() {
            let input: String = String::from("hello world");
            let closure = |s: &str| -> &str {&s[..]};
            let output: &str = closure(&input);
            assert_eq!(input, output);
        }
    }
    
    error: lifetime may not live long enough
       --> src/payload.rs:173:42
        |
        |         let closure = |s: &str| -> &str {&s[..]};
        |                           -        -     ^^^^^^ returning this value requires that `'1` must outlive `'2`
        |                           |        |
        |                           |        let's call the lifetime of this reference `'2`
        |                           let's call the lifetime of this reference `'1`
    

    Deleted user at 2021-05-13 23:57:25

  9. Isn't the compiler capable of detecting that the argument outlives the closure? Example:

    fn main() {
        let x = SimpleWrapper { value: 10 };
        let foo = |wrapper: &SimpleWrapper| wrapper;
        let y = foo(&x);
    }
    
    struct SimpleWrapper {
        value: i32,
    }
    

    Nico Teufel at 2022-02-03 00:40:34

  10. Sometimes, I use a “cast function” to declare lifetimes:

    fn main() {
        fn cast<T>(x: T) -> T
            where T: for<'a> Fn(&'a SimpleWrapper) -> &'a SimpleWrapper
        {
            x
        }
    
        let x = SimpleWrapper { value: 10 };
        let foo = cast(|wrapper: &SimpleWrapper| wrapper);
        let y = foo(&x);
    }
    
    struct SimpleWrapper {
        value: i32,
    }
    

    Jörg Sommer at 2022-05-29 05:49:00