Can I have a `Fn` that returns a future referencing the closure's arguments?

I have several async fns that take a &mut File and performs different operations on it, such as:

async fn write_some_magic_data(file: &mut File) -> std::io::Result<()> {
    file.write_all(b"Magic").await
}

Now I want a generic fn that repeatedly performs the same operation, such as:

async fn write_forever<'a, Fut>(
    mut file: File,
    mut write_fn: impl FnMut(&'a mut File) -> Fut,
) -> std::io::Result<()>
where
    Fut: Future<Output = std::io::Result<()>> + 'a,
{
    loop {
        write_fn(&mut file).await?;
    }
}

But the above code fails to meet the lifetime requirements (playground link):

error[E0499]: cannot borrow `file` as mutable more than once at a time
  --> src/lib.rs:12:18
   |
4  | async fn write_forever<'a, Fut>(
   |                        -- lifetime `'a` defined here
...
12 |         write_fn(&mut file).await?;
   |         ---------^^^^^^^^^-
   |         |        |
   |         |        `file` was mutably borrowed here in the previous iteration of the loop
   |         argument requires that `file` is borrowed for `'a`

For more information about this error, try `rustc --explain E0499`.

I couldn't see the problem here, since although the returned Fut contains a borrow to the File, the future object should be automatically dropped after its completion, so the borrow should also be released.

Is it possible to make the above code compile, or am I missing something important?

Unfortunately you can't easily write down the signature you're looking for. It is possible to write it using a helper trait though:

async fn write_forever(
    mut file: File,
    mut write_fn: impl for<'a> AsyncFnMut<&'a mut File, Output = std::io::Result<()>>,
) -> std::io::Result<()> {
    loop {
        write_fn.call(&mut file).await?;
    }
}

trait AsyncFnMut<T> {
    type Fut: Future<Output = Self::Output>;
    type Output;
    
    fn call(&mut self, arg: T) -> Self::Fut;
}

impl<F, Fut, T> AsyncFnMut<T> for F
where
    F: FnMut(T) -> Fut,
    Fut: Future,
{
    type Fut = Fut;
    type Output = Fut::Output;
    
    fn call(&mut self, arg: T) -> Fut {
        (self)(arg)
    }
}

playground

Unfortunately the above helper trait only works with real functions. If you try to use a closure, the compiler will throw a type inference error because it's too stupid.

Another option is to use a boxed future instead:

use futures::future::BoxFuture;

async fn write_forever(
    mut file: File,
    mut write_fn: impl for<'a> FnMut(&'a mut File) -> BoxFuture<'a, std::io::Result<()>>,
) -> std::io::Result<()> {
    loop {
        write_fn(&mut file).await?;
    }
}

playground

However it requires wrapping all functions you pass it in a helper that boxes the future:

write_forever(file, |file| Box::pin(write_some_magic_data(file))).await;

It also has a runtime cost due to the boxing.

1 Like

That's an awesome solution! Thank you (and Happy New Year :grinning:)!

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.