Skip to content

Borrow checker bug when matching select().await with &mut references #114168

@MatrixDev

Description

@MatrixDev

I've tried to simplify this example to the best of my abilities so here it goes. For some reason borrow checker doesn't treat match as taking ownership / consuming the argument:

async fn job(value: &mut usize) -> usize {
    *value
}

let mut value1 = 42;
let mut value2 = 42;
let mut future1 = Box::pin(job(&mut value1));
let mut future2 = Box::pin(job(&mut value2));

loop {
    let result = select(future1, future2).await;
    match result {
        Either::Left(e) => println!("{}", e.0),
        Either::Right(e) => println!("{}", e.0),
    }

    future1 = Box::pin(job(&mut value1)); // ERROR: cannot borrow `value1` as mutable more than once at a time [E0499]
    future2 = Box::pin(job(&mut value2)); // ERROR: cannot borrow `value1` as mutable more than once at a time [E0499]
}

The most weird thing I found is that this issue can be worked around with a very simple trick:

async fn job(value: &mut usize) -> usize {
    *value
}

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

let mut value1 = 42;
let mut value2 = 42;
let mut future1 = Box::pin(job(&mut value1));
let mut future2 = Box::pin(job(&mut value2));

loop {
    let result = select(future1, future2).await;
    match noop(result) { // explicitly consuming and returning result fixes borrowing errors
        Either::Left(e) => println!("{}", e.0),
        Either::Right(e) => println!("{}", e.0),
    }

    future1 = Box::pin(job(&mut value1)); // NO ERROR
    future2 = Box::pin(job(&mut value2)); // NO ERROR
}

I expected to see this happen:
Match must consume all &mut references and allow me to create new &mut references after it.

Instead, this happened:
It seams that borrow checker doesn't think that match consumed &mut references.

Meta

Bug reproduces on both stable and nightly versions.

rustc --version --verbose:

rustc 1.71.0 (8ede3aae2 2023-07-12)
binary: rustc
commit-hash: 8ede3aae28fe6e4d52b38157d7bfe0d3bceef225
commit-date: 2023-07-12
host: aarch64-apple-darwin
release: 1.71.0
LLVM version: 16.0.5
Backtrace

    Blocking waiting for file lock on build directory
   Compiling futures-sink v0.3.28
   Compiling pin-project-lite v0.2.10
   Compiling pin-utils v0.1.0
   Compiling futures-core v0.3.28
   Compiling memchr v2.5.0
   Compiling futures-task v0.3.28
   Compiling futures-io v0.3.28
   Compiling slab v0.4.8
   Compiling futures-channel v0.3.28
   Compiling futures-util v0.3.28
   Compiling futures-executor v0.3.28
   Compiling futures v0.3.28
   Compiling rust-test v0.1.0 (/Users/matrixdev/Projects/rust-test)
error[E0499]: cannot borrow `value1` as mutable more than once at a time
  --> src/main.rs:20:32
   |
10 |     let mut future1 = Box::pin(job(&mut value1));
   |                                    ----------- first mutable borrow occurs here
...
20 |         future1 = Box::pin(job(&mut value1));
   |                                ^^^^^^^^^^^ second mutable borrow occurs here
21 |         future2 = Box::pin(job(&mut value2));
22 |     }
   |     - first borrow might be used here, when `result` is dropped and runs the destructor for type `Either<(usize, Pin<Box<impl futures::Future<Output = usize>>>), (usize, Pin<Box<impl futures::Future<Output = usize>>>)>`

error[E0499]: cannot borrow `value2` as mutable more than once at a time
  --> src/main.rs:21:32
   |
11 |     let mut future2 = Box::pin(job(&mut value2));
   |                                    ----------- first mutable borrow occurs here
...
21 |         future2 = Box::pin(job(&mut value2));
   |                                ^^^^^^^^^^^ second mutable borrow occurs here
22 |     }
   |     - first borrow might be used here, when `result` is dropped and runs the destructor for type `Either<(usize, Pin<Box<impl futures::Future<Output = usize>>>), (usize, Pin<Box<impl futures::Future<Output = usize>>>)>`

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-async-awaitArea: Async & AwaitA-borrow-checkerArea: The borrow checkerAsyncAwait-TriagedAsync-await issues that have been triaged during a working group meeting.C-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions