7
votes

I have not found any rules in the Rust documentation that would explain how lifetime elision applies to closures. Let's take a simple example:

fn foo(s: &str) {
    let id = |x: &str| x;
    println!("{}", id(s));
}

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

I thought that the closure in the foo function would work similar to the following code:

fn foo(s: &str) {
    struct Id;  // A helper structure for closure
    impl Id {
        fn id(self: Self, x: &str) -> &str { &x }
    }
    let id = Id; // Creating a closure
    println!("{}", id.id(s));
}

The latter works fine, but the former fails to compile and produces a long error message about conflicting lifetime requirements:

t3.rs:2:24: 2:25 error: cannot infer an appropriate lifetime due to conflicting requirements [E0495]
t3.rs:2     let id = |x: &str| x;
                               ^
t3.rs:2:24: 2:25 note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the block at 2:23...
t3.rs:2     let id = |x: &str| x;
                               ^
t3.rs:2:24: 2:25 note: ...so that expression is assignable (expected `&str`, found `&str`)
t3.rs:2     let id = |x: &str| x;
                               ^
<std macros>:3:11: 3:36 note: but, the lifetime must be valid for the expression at 3:10...
<std macros>:3 print ! ( concat ! ( $ fmt , "\n" ) , $ ( $ arg ) * ) ) ;
                         ^~~~~~~~~~~~~~~~~~~~~~~~~
<std macros>:2:25: 2:56 note: in this expansion of format_args!
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
t3.rs:3:5: 3:27 note: in this expansion of println! (defined in <std macros>)
<std macros>:3:11: 3:36 note: ...so type `(&&str,)` of expression is valid during the expression
<std macros>:3 print ! ( concat ! ( $ fmt , "\n" ) , $ ( $ arg ) * ) ) ;
                         ^~~~~~~~~~~~~~~~~~~~~~~~~
<std macros>:2:25: 2:56 note: in this expansion of format_args!
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
t3.rs:3:5: 3:27 note: in this expansion of println! (defined in <std macros>)
error: aborting due to previous error

I wonder why Rust cannot infer the proper lifetime in simple closures like the one that I wrote above. Moreover, why does the compiler think that there are conflicting requirements for lifetime.

2
Remove the : &str and it works. The &str there doesn’t mean what you think it means. I have no time to explain now, however, as I should be in bed.Chris Morgan
Actually, I meant '&str', because it is necessary in a bit more complex case. In any case, my question was not how to get this trivial example to work, but what are the rules here? Why does the compiler finds the conflicting requirements here?svat
@ChrisMorgan: If you have some time, it would be great if you could explain what's going on. I have a feeling it could be due to an inferred for<'a> but it's not quite clear... and the lack of answer after 20 hours seems to mean I am not the only one unsure of what's going on ^^Matthieu M.

2 Answers

3
votes

When you specify the type of a parameter or the return type in a closure, and that type is a reference, the compiler sets wrong expectations on the implicit lifetime parameter, and there's no way to define the lifetime parameter explicitly on a closure. This is a known issue. The workaround is to omit the parameter's type or the return type and let the compiler infer everything.

fn foo(s: &str) {
    let id = |x| x;
    println!("{}", id(s));
}

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

If you still need to give a type hint, you can do so with a let binding inside the closure:

fn foo(s: &str) {
    let id = |x| { let y: &str = x; y };
    println!("{}", id(s));
}

fn main() {
    foo("string");
}
0
votes

The closure can't infer the lifetime from the method signature. Effectively you're defining a lifetime in the method signature let's say it's implicitly 'a, and another lifetime, implicitly 'b in the closure.

Match up the lifetimes and it will compile.

fn foo<'a>(s: &'a str) {
    let id = |x: &'a str| x;
    println!("{}", id(s))
}

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