How to reuse/update futures in the loop when using select?

I'm playing with Rust for almost two years already but borrow checker sometimes just wants to drive me insane...

Playgound:

Idea is simple - I have two jobs. job1 - one takes &mut to some value (in real application this is a non-cloneable struct) and somewhere in the future will return a string. If string is received I must cancel previous job2 and start with new value. If no value is present - job2 starts with no value. job1 and job2 must always run.

let mut value = 42;
let mut future1 = Box::pin(job1(&mut value));
let mut future2 = Box::pin(job2(None));

loop {
    let results = select(future1, future2).await;
    match results {
        Either::Left(result) => {
            future1 = Box::pin(job1(&mut value)); // ERROR: error[E0499]: cannot borrow `value` as mutable more than once at a time
            future2 = Box::pin(job2(Some(result.0)));
        }
        Either::Right(result) => {
            future1 = result.1;
            future2 = Box::pin(job2(None));
        }
    }
}
error[E0499]: cannot borrow `value` as mutable more than once at a time
  --> src/main.rs:19:41
   |
12 |     let mut future1 = Box::pin(job1(&mut value));
   |                                     ---------- first mutable borrow occurs here
...
19 |                 future1 = Box::pin(job1(&mut value));
   |                                         ^^^^^^^^^^ second mutable borrow occurs here
...
27 |     }
   |     - first borrow might be used here, when `results` is dropped and runs the destructor for type `Either<(String, Pin<Box<impl futures::Future<Output = ()>>>), ((), Pin<Box<impl futures::Future<Output = String>>>)>`

For more information about this error, try `rustc --explain E0499`.
error: could not compile `playground` (bin "playground") due to previous error

I understand why - Either might hold a reference to future1 in the Either::Right and rust enums can't have separate lifetimes per variant. So lets try to fix it:

let mut value = 42;
let mut future1 = Box::pin(job1(&mut value));
let mut future2 = Box::pin(job2(None));

loop {
    let select_result = select(future1, future2).await;
    let left_result = match select_result {
        Either::Left(e) => e,
        Either::Right(result) => {
            future1 = result.1;
            future2 = Box::pin(job2(None));
            continue;
        },
    };

    future1 = Box::pin(job1(&mut value)); // ERROR: error[E0499]: cannot borrow `value` as mutable more than once at a time
    future2 = Box::pin(job2(Some(left_result.0)));
}
error[E0499]: cannot borrow `value` as mutable more than once at a time
  --> src/main.rs:46:33
   |
32 |     let mut future1 = Box::pin(job1(&mut value));
   |                                     ---------- first mutable borrow occurs here
...
46 |         future1 = Box::pin(job1(&mut value));
   |                                 ^^^^^^^^^^ second mutable borrow occurs here
47 |         future2 = Box::pin(job2(Some(left_result.0)));
48 |     }
   |     - first borrow might be used here, when `select_result` is dropped and runs the destructor for type `Either<(String, Pin<Box<impl futures::Future<Output = ()>>>), ((), Pin<Box<impl futures::Future<Output = String>>>)>`

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

Same error. But if I understand everything correctly any &mut to value must be dead when I try to get new &mut. But nope... Borrow checker doesn't agree with me.

What is interesting is that this runs without problems:

ut value = 42;
let mut future = Box::pin(job1(&mut value));

loop {
    future.await;
    future = Box::pin(job1(&mut value));
}

What am I getting wrong and what is the "most painless" workaround to fix it?

What am I getting wrong

The borrow checker does not track precise data flow. It does not care that dropping future1 drops the only borrow of value; you'll never be able to borrow value again within the loop.

and what is the "most painless" workaround to fix it?

I would switch to "run-time borrow checking": put value in a Mutex. Then it will work as long as you make sure to drop the future (containing the old mutex guard) before creating the new mutex guard.

(Don't think of this as the “non-Rusty” solution; it is very common to need to do something like this when you get into high-level “scheduling which tasks run” problems.)

I have a feeling there might also be a way to rewrite the code in terms of unboxed futures and select! that would work, but I'm not immediately thinking of it.

1 Like

em.... why then this code works? Future is consumed and I can reference value after that.

loop {
    future.await;
    // drop(future); <- this works as well
    future = Box::pin(job1(&mut value));
}

This would not work (because guard will be dropped after each iteration) unless I also put value in Rc/Arc and clone it instead of passing by reference.

It tracks some data flow. “The entire value that held the borrow was consumed” is some of what it can track. I didn't mean to say that nothing can work here; only that you should expect the borrow checker to often reject programs that “should be okay” in this kind of situation.

This would not work (because guard will be dropped after each iteration)

Put the guard inside an async block which owns it.

let mut value = Mutex::new(42);
let mut future1 = Box::pin(async {
    job1(&mut mutex.try_lock().unwrap()).await
});

I found a very very wierd solution:

fn noop<T>(value: T) -> T {
    value
}

async fn test() {
    let mut value = 42;
    let mut future1 = Box::pin(job1(&mut value));
    let mut future2 = Box::pin(job2(None));

    loop {
        let select_result = select(future1, future2).await;
        let left_result = match noop(select_result) {
            Either::Left(e) => e,
            Either::Right(result) => {
                future1 = result.1;
                future2 = Box::pin(job2(None));
                continue;
            },
        };

        future1 = Box::pin(job1(&mut value));
        future2 = Box::pin(job2(Some(left_result.0)));
    }
}

Now everything works and no borrowing problems... This looks like a bug with how borrow checker works with match.

Aha! This is not a bug, but an intentional behavior of match, that all temporaries in the scrutinee expression (that produces the value being matched) are not dropped until the end of the match. This is done so that you can, for example, write

match some_fn_allocates_string().as_str() {
    "a" => ...

without the string being dropped before you're done with it. You should not need the noop function; the important part is that you moved the await outside the match.

Are you sure? All my examples had await outside of match. Also your point doesn't really makes sense - you're saying that "temporary" should live until the end of match but in all my examples I asked about error was appearing after the match.

Just to show what I mean. This is with noop (no error):

This is without noop (error when borrowing):

Both examples have borrowing after the match (all temporaries must be dead) and await outside of match.

Ugh. That makes no sense. You may wish to file a bug report.

Sorry; I didn't scroll back far enough and assumed you had previously written it the way I said.

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.