Why does the compiler think that a function can return a passed closure, even if the lifetimes are independent?

Consider the signature of the function calls_closure below. To me it looks like that there is no way that closure: impl FnOnce passed to it can make its way to its return value, as both are declared to have independent lifetimes. If anything related to closure was returned by calls_closure, I would expect a 'closure: 'result bound. But the function's signature does not have any.

Still, the compiler complains that when calling calls_closure from creates_closure the lifetime 'closure_state which is captured by the closure could make its way through calls_closure and then be returned by creates_closure.

To me, this looks like a contradiction. Can anyone explain what is going on here? Is there anything "magic" about closure and lifetimes I am not aware of? Is there a way to add lifetime constraints only to calls_closure to make this exact code compile (the idea is that creates_closure's return value is lifetime-independent of closure_state)?

// the compiler thinks that the closure could be returned by by this function
// to me, the function signature indicates that the closure and the return value are independent
fn calls_closure<'closure, 'result>(
    closure: impl 'closure + FnOnce() -> (),
) -> impl 'result + Iterator<Item = i32> {
    closure();
    [2].into_iter()
}

fn creates_closure<'closure_state, 'result>(
    closure_state: &'closure_state mut i32,
) -> impl 'result + Iterator<Item = i32> {
    calls_closure(|| {
        *closure_state += 1;
    })
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error: lifetime may not live long enough
  --> src/lib.rs:13:5
   |
10 |   fn creates_closure<'closure_state, 'result>(
   |                      --------------  ------- lifetime `'result` defined here
   |                      |
   |                      lifetime `'closure_state` defined here
...
13 | /     calls_closure(|| {
14 | |         *closure_state += 1;
15 | |     })
   | |______^ function was supposed to return data with lifetime `'result` but it is returning data with lifetime `'closure_state`
   |
   = help: consider adding the following bound: `'closure_state: 'result`

error: could not compile `playground` due to previous error

The solution to your problem is quite literally in the error message.

Quite the opposite, since you have not specified the bounds, the compiler assumes the worse scenario and hence complains.

The compiler proposes to change the second function creates_closure, but that is the one I do not want to change. I want to communicate to the compiler that in the first function calls_closure, the closure gets "consumed" in a way that nothing that is borrowed by the closure gets returned by calls_closure.

This feels like a bug in the borrow checker/lifetime system.

I would expect the following snippet to compile because the closure passed to calls_closure() is completely unrelated to the impl Iterator<...> we return. I've even added a + 'static to indicate that the returned iterator only borrows from a 'static variable ([2] should be promoted to a const by the compiler via "rvalue static promotion").

fn calls_closure(closure: impl FnOnce()) -> impl Iterator<Item = i32> + 'static {
    closure();
    [2].into_iter()
}

fn creates_closure(closure_state: &mut i32) -> impl Iterator<Item = i32> + 'static {
    calls_closure(|| {
        *closure_state += 1;
    })
}

(playground)

   Compiling playground v0.0.1 (/playground)
error: lifetime may not live long enough
 --> src/lib.rs:7:5
  |
6 |   fn creates_closure(closure_state: &mut i32) -> impl Iterator<Item = i32> + 'static {
  |                                     - let's call the lifetime of this reference `'1`
7 | /     calls_closure(|| {
8 | |         *closure_state += 1;
9 | |     })
  | |______^ returning this value requires that `'1` must outlive `'static`
  |
help: consider changing the `impl Trait`'s explicit `'static` bound to the lifetime of argument `closure_state`
  |
6 |  creates_closure(closure_state: &mut i32) -> impl Iterator<Item = i32> + '_fn {
  |                                                                            ~~
help: alternatively, add an explicit `'static` bound to this reference
  |
6 | fn creates_closure(closure_state: &'static mut i32) -> impl Iterator<Item = i32> + 'static {
  |                                   ~~~~~~~~~~~~~~~~

error: could not compile `playground` due to previous error
Standard Output
2 Likes

Thanks, I opened an issue: Function leaks passed closure, even if lifetimes are declared to be independent · Issue #102848 · rust-lang/rust · GitHub


Apparently this is an old issue. There is a workaround described here.

That unfortunately does not seem to work in my case. I guess I will just have to name the type explicitly.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.