Mutable borrow in a loop

I'm trying to write the following generic function:

async fn wait_for<'b, 'a: 'b, F, A, T>(arg: &'a mut A, mut func: F) -> Result<()>
where
    F: FnMut(&'b mut A) -> T,
    T: Future<Output = Result<bool>> + 'a,
{
    let timeout_instant = Instant::now() + TIMEOUT_DURATION;
    while Instant::now() < timeout_instant {
        if let Ok(boolean) = func(arg).await {
            if boolean {
                return Ok(());
            }
        }
        log::info!("Waiting {POLL_DURATION:?}");
        tokio::time::sleep(POLL_DURATION).await;
    }
    bail!("Timed out after {POLL_DURATION:?}")
}

To wait for a condition with a timeout. This fails to compile because I cannot convince the compiler that the borrow of arg only lasts for the duration of the function call:

cannot borrow `*arg` as mutable more than once at a time
`*arg` was mutably borrowed here in the previous iteration of the loop

Unfortunately the mutability of the reference cannot be removed. How do I tell the compiler that the borrow of arg only lasts for the duration of func in each loop iteration?

By using a parameter lifetime 'b, from outside this function body, you're promising that F is allowed to use that reference for that entire lifetime. You can't make that promise across multiple calls to F.

You can use a more flexible lifetime with F: for<'b> FnMut(&'b mut A) -> T, so 'b is different between each call. This is the same as what you get if you omit the lifetime, F: FnMut(&mut A) -> T.

3 Likes

Thank you. Unfortunately when I remove the bound, I get the error that I think I was originally trying to resolve, which is helping the compiler understand that the arg outlives the future wait_for returns:

// roughly...
async fn code(
    clients: &mut [C]
) -> Result<()> {
    for (id, c) in clients.iter_mut().enumerate() {
wait_for(c, |client| async {
            my_func(client).await?
        }).await?;
}
}

results in

lifetime may not live long enough
returning this value requires that `'1` must outlive `'2`
code.rs(183, 31): has type `&'1 mut C`
code.rs(183, 37): return type of closure `impl futures::Future<Output = std::result::Result<bool, anyhow::Error>>` contains a lifetime `'2`

How do I get over this one?

Small tip on how to ask better questions: Include a complete error message as outputted by running cargo check in the terminal. Also, properly indented code will be a lot more readable.

What you’re running into here is the currently still – unfortunately – pretty bad support for “async-closure”-style arguments, where you’d want the Future return type of the closure to be able to borrow the arguments, but there’s no good way to express this as trait bounds with Fn… and Future, and even the not-so-good but technically correct way of expressing this bound anyways is impractical to use.

The practical suggestion thus is to work with boxed futures instead. See the next answer in the same thread I linked above for some examples. In addition to that answer, note that you can also use the futures::FutureExt::boxed method for a post-fix (and slightly less ambiguously typed w.r.t. conversion to the trait object) alternative of using Box::pin(…).

1 Like