Why closure cannot return a reference to data moved into closure

fn main() {
    let data = 20;
    let c = || &data;
    c();
}

The above code return a reference to a variable captured, it compiles. But after adding a move keyword before the closure, it fails to compile.

fn main() {
    let data = 20;
    let c = move || &data;
    c();
}
error: lifetime may not live long enough
 --> src/bin/clo.rs:3:21
  |
3 |     let c = move || &data;
  |             ------- ^^^^^ returning this value requires that `'1` must outlive `'2`
  |             |     |
  |             |     return type of closure is &'2 i32
  |             lifetime `'1` represents this closure's body
  |
  = note: closure implements `Fn`, so references to captured variables can't escape the closure

From what i can tell, the move keyword just move captured variables to closure, when we return a refrence to the captured variable, we are actually returning a reference to one of the closure struct's field, as long as the closure is not dropped before using the returned reference, all should be fine.
So why is this not allowed? Is there any other considerations?

2 Likes

Perhaps the following would be more useful:

struct Thing {
    data: i32
}

impl Thing {
    fn new(data: i32) -> Self {
        Self { data }
    }

    fn bar<'a>(self) -> &'a i32 {
        &self.data
    }
}

Since it's a move closure, the closure "owns" data. Hence, what you're trying to return is essentially a local reference, which is not allowed.

1 Like

Yeah. If the clousure is a FnOnce whose signature consumes the closure, That shouldn't be allowd.

fn call_once(self, args: Args) -> Self::Output;

But what if the closure is just a Fn or FnMut whose signature only borrows the closure.

fn call(&self, args: Args) -> Self::Output;
fn call_mut(&mut self, args: Args) -> Self::Output;

Since the following code is allowed, A Fn/FnMut closure should be allowed to return a ref to closure's field too.

impl Thing{
    fn data_ref(&'a self) -> &'a i32 {
        &self.data
    }
}

Because even Fn is &self -> Output, not &'a self -> Output<'a>. Thus it, by signature, can't return a reference to data inside the closure.

Maybe this could be tweaked with GATs; I don't know.

2 Likes

It would take a larger re-design; FnOnce is a supertrait of Fn, and Fn::call returns <Self as FnOnce<_>>::Output.

3 Likes

It technically doesn't need GATs, though; if we consider that

T : FnMut(Args…) -> Output

is an alias for

for<'any>
    &'any mut T : FnOnce(Args…) -> Output
,

(and same for Fn and &'any T)

then indeed we can notice the current lack of 'any inside Output, but it theoretically could be featured, by dropping the restricted aliases, and using FnOnce directly:

for<'any>
    &'any F : FnOnce() -> &'any i32
,

for instance.

But the current "literal closure" unsugaring will never feature such higher-order bounds.


A practical solution: split the closure and the data it borrows

pub
struct SelfBorrowingClosure<State, F>
where
    // at type-definition site to nudge call-sites to make it higher-order,
    F : Fn(&'_ State) -> &'_ i32,
{
    pub
    state: State,

    pub
    call: F,
}

impl<State, F> SelfBorrowingClosure<State, F>
where
    F : Fn(&'_ State) -> &'_ i32,
{
    pub
    fn call (self: &'_ SelfBorrowingClosure<State, F>)
      -> &'_ i32
    {
        (self.call)(&self.state)
    }
}

with it, you can then write:

fn main ()
{
    // Let's use a block like this to justify ownership
    // over the simpler `let c = || &data[0]` borrowing `data`.
    let c = {
        let data = [42, 27];
        SelfBorrowingClosure {
            state: data,
            call: |data| &data[0],
        }
    };
    assert_eq!(c.call(), &42);
}
3 Likes

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.