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())
}
}