Borrow of owned !Sync type in async function

⚓ Rust    📅 2025-09-05    👤 surdeus    👁️ 3      

surdeus

The following program fails to compile because the future returned by asdf is not Send. b (&Cell) is not Send because Cell is not Sync, and this borrow is held across an await point, therefore the future is not Send.

use std::cell::Cell;

fn must_send(_: impl Send) {}

async fn asdf() {
    let a = Cell::new(1);
    let b = &a;
    async {}.await;
    // let b = &a;
    println!("{}", b.get());
}

fn main() {
    let _ = must_send(asdf());
}

Playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=0587e412c0ff548b37a8e26976bcd2ce

This makes sense to me if the async function doesn't own the Cell. For example, if you had an async function that accepted a &T where T: !Sync, you cannot poll the future on another thread since that would allow you to access T on multiple threads simultaneously.

However, in this case, the async function has ownership over T. Even if you were to poll the future on another thread, T and &T would "move" together, which I believe means that it would not be possible to simultaneously access T from multiple threads. Therefore, even though &T is not Send, you would still be able to safely hold &T across an await point in a Send future, assuming that the async function owns T.

Is my reasoning correct here? Is there a case where it would not be safe to hold a &T borrow (where T: !Sync) across an await point in a Send future while having ownership of T? If this is in fact safe, is this just one of those cases where it is not worth it for the compiler to incur more complexity to handle such things?

I encountered this with something like the following code:

#[axum::debug_handler]
async fn handle_something(headers: HeaderMap, request: Request) -> Response {
    let the_response = || request.uri().to_string().into_response();
    // closure the_response borrows `request`, which is not Sync, across await point
    // can fix this by doing `let uri = request.uri()` then borrowing `uri` in closure
    if !something(&headers) {
        return the_response();
    }
    if !async_something(&headers).await { // <-- await point here
        the_response() // <-- closure used here
    } else {
        "ok".into_response()
    }
}

3 posts - 2 participants

Read full topic

🏷️ Rust_feed