stevs
stevs

Reputation: 33

Why does one of these two very similar async Rust functions trigger thread safety errors?

I'm trying something as a learn-Rust project, where I have a library that consumes some REST APIs through a HTTP request trait that I planned to fill in separately for native and webassembly usage, so that I could have bindings for this library in different environments.

My problem arises in the WASM portion, where I'm trying to adapt the fetch example here:

pub async fn run(url: String, authz: String) -> Result<serde_json::Value, JsValue> {
    let mut opts = RequestInit::new();
    opts.method("GET");
    opts.mode(RequestMode::Cors);

    let request = Request::new_with_str_and_init(&url, &opts)?;

    request.headers().set("Authorization", &authz)?;

    let window = web_sys::window().unwrap();
    let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;

    assert!(resp_value.is_instance_of::<Response>());
    let resp: Response = resp_value.dyn_into().unwrap();

    let json = JsFuture::from(resp.json()?).await?;

    let parsed: serde_json::Value = json.into_serde().unwrap();
    Ok(parsed)
}

I made some light adaptations to suit my purpose here, but it works fine. What doesn't work is my attempt to shoehorn this into the trait interface. The return type is an anyhow::Result, and I unwrap() a bunch of stuff I shouldn't here to temporarily avoid issues with the error type compatibility:

#[async_trait]
impl FetchRequest for WasmFetchRequest {
    async fn get(&mut self) -> Result<serde_json::Value> {
        let mut opts = RequestInit::new();
        opts.method("GET");
        opts.mode(RequestMode::Cors);

        let request = Request::new_with_str_and_init(&self.path, &opts).unwrap();

        request.headers().set("Authorization", &self.authz);

        let window = web_sys::window().unwrap();
        let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.unwrap();

        assert!(resp_value.is_instance_of::<Response>());
        let resp: Response = resp_value.dyn_into().unwrap();

        let json = JsFuture::from(resp.json().unwrap()).await.unwrap();

        let parsed: serde_json::Value = resp.into_serde().unwrap();
        Ok(parsed)
        /*
        // hoped this might work too, same errors
        Ok(run(
            self.path.to_string(),
            self.authz.to_string()
        ).await.map_err(|err| err.into())?);
        */
    }
}

The build is unhappy:

error: future cannot be sent between threads safely
  --> src/lib.rs:66:58
   |
66 |       async fn get(&mut self) -> Result<serde_json::Value> {
   |  __________________________________________________________^
67 | |         /*
68 | |         Ok(run(
69 | |             self.path.to_string(),
...  |
91 | |         Ok(parsed)
92 | |     }
   | |_____^ future created by async block is not `Send`
   |
   = help: within `impl Future`, the trait `Send` is not implemented for `Rc<RefCell<wasm_bindgen_futures::Inner>>`
note: future is not `Send` as it awaits another future which is not `Send`
  --> src/lib.rs:83:26
   |
83 |         let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.unwrap();
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here on type `JsFuture`, which is not `Send`
   = note: required for the cast to the object type `dyn Future<Output = Result<Value, rescale_core::Error>> + Send`

error: future cannot be sent between threads safely
  --> src/lib.rs:66:58
   |
66 |       async fn get(&mut self) -> Result<serde_json::Value> {
   |  __________________________________________________________^
67 | |         /*
68 | |         Ok(run(
69 | |             self.path.to_string(),
...  |
91 | |         Ok(parsed)
92 | |     }
   | |_____^ future created by async block is not `Send`
   |
   = help: within `impl Future`, the trait `Send` is not implemented for `*mut u8`
note: future is not `Send` as this value is used across an await
  --> src/lib.rs:88:20
   |
83 |         let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.unwrap();
   |             ---------- has type `wasm_bindgen::JsValue` which is not `Send`
...
88 |         let json = JsFuture::from(resp.json().unwrap()).await.unwrap();
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here, with `resp_value` maybe used later
...
92 |     }
   |     - `resp_value` is later dropped here
   = note: required for the cast to the object type `dyn Future<Output = Result<Value, rescale_core::Error>> + Send`

error: future cannot be sent between threads safely
  --> src/lib.rs:66:58
   |
66 |       async fn get(&mut self) -> Result<serde_json::Value> {
   |  __________________________________________________________^
67 | |         /*
68 | |         Ok(run(
69 | |             self.path.to_string(),
...  |
91 | |         Ok(parsed)
92 | |     }
   | |_____^ future created by async block is not `Send`
   |
   = help: within `Request`, the trait `Sync` is not implemented for `*mut u8`
note: future is not `Send` as this value is used across an await
  --> src/lib.rs:83:26
   |
83 |         let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.unwrap();
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ first, await occurs here, with `&request` maybe used later...
note: `&request` is later dropped here
  --> src/lib.rs:83:92
   |
83 |         let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.unwrap();
   |                                                                   --------                 ^
   |                                                                   |
   |                                                                   has type `&Request` which is not `Send`
help: consider moving this into a `let` binding to create a shorter lived borrow
  --> src/lib.rs:83:41
   |
83 |         let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.unwrap();
   |                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: required for the cast to the object type `dyn Future<Output = Result<Value, rescale_core::Error>> + Send`

What is the pertinent difference here that causes these errors? I think it must be something with &mut self or else the return type, but I'm out of my depth.

Probably not relevant, but the native counterpart fits into the interface happily enough:

#[async_trait]
impl FetchRequest for NativeFetchRequest {
    async fn get(&mut self) -> Result<serde_json::Value> {
        let client = reqwest::Client::new();
        let mut req = client.get(&self.path);
        req = req.header("Authorization", self.authz.as_str());
        let res = req.send().await?;
        res.error_for_status()?
            .json::<serde_json::Value>().await
            .map_err(|err| err.into())
    }
}

Upvotes: 3

Views: 788

Answers (1)

user4815162342
user4815162342

Reputation: 155216

According to async_trait documentation, futures returned by async trait methods must by default be Send:

Async fns get transformed into methods that return Pin<Box<dyn Future + Send + 'async>> and delegate to a private async freestanding function.

Your async fn produced a non-Send future. So the difference between your original code and the one that uses async_trait was that the original code didn't require a Send future, it was okay with non-Send ones, whereas async_trait by default expects Send futures.

To fix the issue, you need to tell async_trait not to require Send using #[async_trait(?Send)] on the trait and the impl block. In other words, replace #[async_trait] with #[async_trait(?Send)] in both the trait declaration and the implementation, and your code should compile. (Playground.)

Upvotes: 6

Related Questions