2

Since tokio::select! runs all branches concurrently but not in parallel, why doesn't Rust allow branches to share the same mutable reference ? Is there a way to solve this problem ?

Example of code that does not compile:

async fn fun1(_var: &mut usize) {}
async fn fun2(_var: &mut usize) {}

#[tokio::main]
async fn main() {
    let mut a = 0;
    tokio::select! {
        _ = fun1(&mut a) => {}
        _ = fun2(&mut a) => {}
    };
}
1
  • 1
    Even if the futures are running concurrently (not in parallel), they are holding the same mutable reference over the same period and interleaving their use of it, which violates the rules. Even a trivial example shows that's not possible regardless of threads, tasks, and whatnot. Commented Aug 10 at 21:56

2 Answers 2

2

The issue here is that the future returned by fun1 and fun2 contain the mutable borrow within, causing the call to fun2 to fail since the future returned by fun1 has ownership of the mutable borrow.

We can see this is the case by changing the definitions of the functions to manually return a future that doesn't keep the mutable borrow (which is what the + use<> means here):

fn fun1(_var: &mut usize) -> impl Future<Output = ()> + use<> { async move {} }
fn fun2(_var: &mut usize) -> impl Future<Output = ()> + use<> { async move {} }

Which makes the select branch compile fine.

This is likely not applicable in your scenario, since I assume you use the mutable borrow inside of the future, In which case the only solution would be some kind of lock (RefCell / Mutex) and passing a shared borrow to each instead.

Sign up to request clarification or add additional context in comments.

Comments

1

why doesn't Rust allow branches to share the same mutable reference?

This is not a problem limited to async functions. You can't do this even in synchronous, single-threaded code. This is a core principle of the Rust borrow checker, and it prevents stuff like this:

let mut v = vec!["foo".to_owned()];
let first = &v[0];
v.clear();
println!("{first}"); // Use after free!

The fact that both futures will not run at the same time is both irrelevant and potentially not even true! A theoretical async runtime that provides scoped tasks could indeed allow one of the functions to spawn a task on another thread, letting that task use the mutable borrow.

Even if that were not true though, the above synchronous example explains succinctly why this is problematic: one future might take a reference to some data structure that the other future invalidates.

If you really need to have shared mutable access, you will have to do it through an interior mutability mechanism (Mutex, et al).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.