Why does Rust complain about a non-Sync type for this async function?

I'm really struggling to understand why this code doesn't compile

use sarangollo_lib::Game;
use async_std::{self, sync::RwLock};
use tide::{Request, Response};

async fn handle_get_state(req: Request<RwLock<Game>>) -> Response {
    let game = req.state().read().await;
    let game_state = game.get_state();
    let result = tide::Response::new(200).body_json(&game_state);
    match result {
        Ok(rsp) => rsp,
        Err(_) => Response::new(500), 
    }
}

#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
    let game = Game::default();
    let state = RwLock::new(game);
    let mut app = tide::with_state(state);
    app.at("/").get(|_| async move { "Hello, world!" });
    app.at("/state").get(|req| async move { handle_get_state(req).await });
    app.listen("127.0.0.1:8080").await?;
    Ok(())
}

The compiler complains with the following:

    Checking sarangollo-lib v0.1.0 (C:\Users\ruben\src\sarangollolib)
error[E0277]: `(dyn futures_io::if_std::AsyncBufRead + std::marker::Send + 'static)` cannot be shared between threads safely
  --> src/main.rs:25:22
   |
25 |     app.at("/state").get(|req| async move { handle_get_state(req).await });
   |                      ^^^ `(dyn futures_io::if_std::AsyncBufRead + std::marker::Send + 'static)` cannot be shared between threads safely
   |
   = help: the trait `std::marker::Sync` is not implemented for `(dyn futures_io::if_std::AsyncBufRead + std::marker::Send + 'static)`
   = note: required because of the requirements on the impl of `std::marker::Sync` for `std::ptr::Unique<(dyn futures_io::if_std::AsyncBufRead + std::marker::Send + 'static)>`
   = note: required because it appears within the type `std::boxed::Box<(dyn futures_io::if_std::AsyncBufRead + std::marker::Send + 'static)>`
   = note: required because it appears within the type `std::pin::Pin<std::boxed::Box<(dyn futures_io::if_std::AsyncBufRead + std::marker::Send + 'static)>>`
   = note: required because it appears within the type `http_service::Body`
   = note: required because it appears within the type `http::request::Request<http_service::Body>`
   = note: required because it appears within the type `tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>`
   = note: required because of the requirements on the impl of `std::marker::Send` for `&tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>`
   = note: required because it appears within the type `for<'r, 's, 't0, 't1, 't2, 't3, 't4> {tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>, &'r tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>, tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>, &'s async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>, &'t0 async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>, impl std::future::Future, impl std::future::Future, ()}`
   = note: required because it appears within the type `[static generator@src/main.rs:5:67: 13:2 req:tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>> for<'r, 's, 't0, 't1, 't2, 't3, 't4> {tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>, &'r tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>, tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>, &'s async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>, &'t0 async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>, impl std::future::Future, impl std::future::Future, ()}]`
   = note: required because it appears within the type `std::future::GenFuture<[static generator@src/main.rs:5:67: 13:2 req:tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>> for<'r, 's, 't0, 't1, 't2, 't3, 't4> {tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>, &'r tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>, tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>, &'s async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>, &'t0 async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>, impl std::future::Future, impl std::future::Future, ()}]>`
   = note: required because it appears within the type `impl std::future::Future`
   = note: required because it appears within the type `impl std::future::Future`
   = note: required because it appears within the type `{tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>, impl std::future::Future, impl std::future::Future, ()}`
   = note: required because it appears within the type `[static generator@src/main.rs:25:43: 25:74 req:tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>> {tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>, impl std::future::Future, impl std::future::Future, ()}]`
   = note: required because it appears within the type `std::future::GenFuture<[static generator@src/main.rs:25:43: 25:74 req:tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>> {tide::request::Request<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>, impl std::future::Future, impl std::future::Future, ()}]>`
   = note: required because it appears within the type `impl std::future::Future`
   = note: required because of the requirements on the impl of `tide::endpoint::Endpoint<async_std::sync::rwlock::RwLock<sarangollo_lib::game::Game>>` for `[closure@src/main.rs:25:26: 25:74]`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: could not compile `sarangollo-lib`.

To learn more, run the command again with --verbose.

However, changing the handler function to the following does compile (notice how reading the Request state and awaiting the read lock are split in two separate lines here)

async fn handle_get_state(req: Request<RwLock<Game>>) -> Response {
    let state = req.state();
    let game = state.read().await;
    let game_state = game.get_state();
    let result = tide::Response::new(200).body_json(&game_state);
    match result {
        Ok(rsp) => rsp,
        Err(_) => Response::new(500), 
    }
}

I presume it has something to do with chaining those requests returning a type that somehow is not Sync (since Request is not Sync by default), which is captured by the underlying Future/Generator that the async function produces.
But I really don't understand why splitting that expression resolves the issue. I would've assumed that the type being awaited on on the fixed code would be the same as the one returned in the faulty code, but that doesn't seem to be the case.

Can someone enlighten me, please?
Thanks!

Variables used in an expression are first dropped at the end of the expression, so when you type req.state().read().await, the return value of req.state() still exists when you reach the await.

Since this value exists during an await point, it must be stored in the future of the async fn, so it not being Sync makes the future not Sync.

Note that |req| async move { handle_get_state(req).await } is the same as just |req| handle_get_state(req)

I was under the impression that calling req.state() would return a &RwLock<Game>, which I assumed would be Sync since it's a reference type.
Is my understanding not correct or is there something else I'm missing?

Sorry for not being more specific with my question. There is something that doesn't want to click in my mind even though I though I had a decent understanding of all this machinery.

I'm not familiar with the request type you are dealing with, but the issue is likely some value being dropped at the end of the line, which in one case is kept alive for too long.

Your understaing is correct, but there is something you are missing: at Rust lowest level, it cannot call req.state() with a single "operation".

Instead, it needs to:

  1. borrow req within an unnamed / anonymous temporary:

    let __anon_req_borrow__: &'_ Request<_> = &req;
    
  2. use that temporary to call .state():

    __anon__req__borrow__.state() ...
    
  3. drop that temporary

    drop(__anon__req__borrow__)
    

This is always the case in Rust (i.e., there is nothing special involving locks or futures).

But, as @alice stated,

To better understand this thing about captures across yield / .await points:

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.