Poll_fn that captures a FnOnce closure

For the sake of argument, suppose you need to implement a future using poll_fn.

The body of poll_fn captures an FnOnce and calls it.

fn make_future(f: impl FnOnce()) -> impl Future<Output = ()> {
    poll_fn(move |cx| {
        f();
        Poll::Ready(())
    })
}

Of course, it does not compile because the argument of poll_fn is a FnMut closure:

error[E0507]: cannot move out of `f`, a captured variable in an `FnMut` closure

However, the code above is perfectly safe because f will be really called only once.

How would you get around this issue?

You can wrap it in an Option.

fn make_future(f: impl FnOnce()) -> impl Future<Output = ()> {
    let mut f = Some(f);
    poll_fn(move |cx| {
        (f.take().unwrap())();
        Poll::Ready(())
    })
}

reverts edit

2 Likes

Does a solution that does not require this wrapping exist, perhaps using unsafe?

No, you need to panic if the future is polled too much, because doing so is a safe operation. async blocks do the same kind of thing, panicking when polled again after completion, and tracking this state in a tag similar to how Option works.

2 Likes

Regarding your edit: I would leave this panicking instead of a silent no-op. If you don't like the non-specific unwrap, you could use expect("future resumed after completion") instead of unwrap, but panicking is very expected behavior of what a future does if polled again after returning Poll::Ready). Every async fn and every async block would do the same (i. e. panic) in this situation.

2 Likes

Yeah I realized my edit is not perfect. Rollbacked and pretend nothing happened :sweat_smile:

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.