Skip to content

Commit

Permalink
Merge pull request #2 from rustwasm/master
Browse files Browse the repository at this point in the history
Add support for `#[wasm_bindgen]` on `async fn` (rustwasm#1754)
  • Loading branch information
cburgos committed Sep 6, 2019
2 parents 435e16e + 8861811 commit e83ca22
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 18 deletions.
1 change: 1 addition & 0 deletions crates/backend/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ pub struct Function {
pub ret: Option<syn::Type>,
pub rust_attrs: Vec<syn::Attribute>,
pub rust_vis: syn::Visibility,
pub r#async: bool,
}

#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
Expand Down
39 changes: 29 additions & 10 deletions crates/backend/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,13 +442,30 @@ impl TryToTokens for ast::Export {
if let syn::Type::Reference(_) = syn_ret {
bail_span!(syn_ret, "cannot return a borrowed ref with #[wasm_bindgen]",)
}
let ret_ty = quote! {
-> <#syn_ret as wasm_bindgen::convert::ReturnWasmAbi>::Abi
};
let convert_ret = quote! {
<#syn_ret as wasm_bindgen::convert::ReturnWasmAbi>
::return_abi(#ret)

// For an `async` function we always run it through `future_to_promise`
// since we're returning a promise to JS, and this will implicitly
// require that the function returns a `Future<Output = Result<...>>`
let (ret_expr, projection) = if self.function.r#async {
(
quote! {
wasm_bindgen_futures::future_to_promise(async {
wasm_bindgen::__rt::IntoJsResult::into_js_result(#ret.await)
}).into()
},
quote! {
<wasm_bindgen::JsValue as wasm_bindgen::convert::ReturnWasmAbi>
},
)
} else {
(
quote! { #ret },
quote! {
<#syn_ret as wasm_bindgen::convert::ReturnWasmAbi>
},
)
};
let convert_ret = quote! { #projection::return_abi(#ret_expr) };
let describe_ret = quote! {
<#syn_ret as WasmDescribe>::describe();
};
Expand All @@ -457,19 +474,21 @@ impl TryToTokens for ast::Export {

let start_check = if self.start {
quote! {
const _ASSERT: fn() = || #ret_ty { loop {} };
const _ASSERT: fn() = || -> #projection::Abi { loop {} };
}
} else {
quote! {}
};

(quote! {
#(#attrs)*
#[export_name = #export_name]
#[allow(non_snake_case)]
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
#[cfg_attr(
all(target_arch = "wasm32", not(target_os = "emscripten")),
export_name = #export_name,
)]
#[allow(clippy::all)]
pub extern "C" fn #generated_name(#(#args),*) #ret_ty {
pub extern "C" fn #generated_name(#(#args),*) -> #projection::Abi {
#start_check
// Scope all local variables to be destroyed after we call the
// function to ensure that `#convert_ret`, if it panics, doesn't
Expand Down
1 change: 1 addition & 0 deletions crates/macro-support/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,7 @@ fn function_from_decl(
ret,
rust_attrs: attrs,
rust_vis: vis,
r#async: sig.asyncness.is_some(),
},
method_self,
))
Expand Down
1 change: 1 addition & 0 deletions crates/macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ quote = "1.0"
[dev-dependencies]
trybuild = "1.0"
wasm-bindgen = { path = "../..", version = "0.2.50", features = ['strict-macro'] }
wasm-bindgen-futures = { path = "../futures", version = "0.4.0" }
39 changes: 39 additions & 0 deletions crates/macro/ui-tests/async-errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct MyType;

#[wasm_bindgen]
pub async fn good1() { loop {} }
#[wasm_bindgen]
pub async fn good2() -> JsValue { loop {} }
#[wasm_bindgen]
pub async fn good3() -> u32 { loop {} }
#[wasm_bindgen]
pub async fn good4() -> MyType { loop {} }
#[wasm_bindgen]
pub async fn good5() -> Result<(), JsValue> { loop {} }
#[wasm_bindgen]
pub async fn good6() -> Result<JsValue, JsValue> { loop {} }
#[wasm_bindgen]
pub async fn good7() -> Result<u32, JsValue> { loop {} }
#[wasm_bindgen]
pub async fn good8() -> Result<MyType, JsValue> { loop {} }
#[wasm_bindgen]
pub async fn good9() -> Result<MyType, u32> { loop {} }
#[wasm_bindgen]
pub async fn good10() -> Result<MyType, MyType> { loop {} }

pub struct BadType;

#[wasm_bindgen]
pub async fn bad1() -> Result<(), ()> { loop {} }
#[wasm_bindgen]
pub async fn bad2() -> Result<(), BadType> { loop {} }
#[wasm_bindgen]
pub async fn bad3() -> BadType { loop {} }
#[wasm_bindgen]
pub async fn bad4() -> Result<BadType, JsValue> { loop {} }


fn main() {}
50 changes: 50 additions & 0 deletions crates/macro/ui-tests/async-errors.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
error[E0277]: the trait bound `std::result::Result<(), ()>: wasm_bindgen::__rt::IntoJsResult` is not satisfied
--> $DIR/async-errors.rs:29:1
|
29 | #[wasm_bindgen]
| ^^^^^^^^^^^^^^^ the trait `wasm_bindgen::__rt::IntoJsResult` is not implemented for `std::result::Result<(), ()>`
|
= help: the following implementations were found:
<std::result::Result<(), E> as wasm_bindgen::__rt::IntoJsResult>
<std::result::Result<T, E> as wasm_bindgen::__rt::IntoJsResult>
= note: required by `wasm_bindgen::__rt::IntoJsResult::into_js_result`

error[E0277]: the trait bound `std::result::Result<(), BadType>: wasm_bindgen::__rt::IntoJsResult` is not satisfied
--> $DIR/async-errors.rs:31:1
|
31 | #[wasm_bindgen]
| ^^^^^^^^^^^^^^^ the trait `wasm_bindgen::__rt::IntoJsResult` is not implemented for `std::result::Result<(), BadType>`
|
= help: the following implementations were found:
<std::result::Result<(), E> as wasm_bindgen::__rt::IntoJsResult>
<std::result::Result<T, E> as wasm_bindgen::__rt::IntoJsResult>
= note: required by `wasm_bindgen::__rt::IntoJsResult::into_js_result`

error[E0277]: the trait bound `wasm_bindgen::JsValue: std::convert::From<BadType>` is not satisfied
--> $DIR/async-errors.rs:33:1
|
33 | #[wasm_bindgen]
| ^^^^^^^^^^^^^^^ the trait `std::convert::From<BadType>` is not implemented for `wasm_bindgen::JsValue`
|
= help: the following implementations were found:
<wasm_bindgen::JsValue as std::convert::From<&'a T>>
<wasm_bindgen::JsValue as std::convert::From<&'a std::string::String>>
<wasm_bindgen::JsValue as std::convert::From<&'a str>>
<wasm_bindgen::JsValue as std::convert::From<MyType>>
and 62 others
= note: required because of the requirements on the impl of `std::convert::Into<wasm_bindgen::JsValue>` for `BadType`
= note: required because of the requirements on the impl of `wasm_bindgen::__rt::IntoJsResult` for `BadType`
= note: required by `wasm_bindgen::__rt::IntoJsResult::into_js_result`

error[E0277]: the trait bound `std::result::Result<BadType, wasm_bindgen::JsValue>: wasm_bindgen::__rt::IntoJsResult` is not satisfied
--> $DIR/async-errors.rs:35:1
|
35 | #[wasm_bindgen]
| ^^^^^^^^^^^^^^^ the trait `wasm_bindgen::__rt::IntoJsResult` is not implemented for `std::result::Result<BadType, wasm_bindgen::JsValue>`
|
= help: the following implementations were found:
<std::result::Result<(), E> as wasm_bindgen::__rt::IntoJsResult>
<std::result::Result<T, E> as wasm_bindgen::__rt::IntoJsResult>
= note: required by `wasm_bindgen::__rt::IntoJsResult::into_js_result`

For more information about this error, try `rustc --explain E0277`.
1 change: 1 addition & 0 deletions crates/webidl/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ impl<'src> FirstPassRecord<'src> {
ret: ret.clone(),
rust_attrs: vec![],
rust_vis: public(),
r#async: false,
},
rust_name: rust_ident(rust_name),
js_ret: js_ret.clone(),
Expand Down
8 changes: 1 addition & 7 deletions examples/fetch/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use js_sys::Promise;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::future_to_promise;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response};

Expand Down Expand Up @@ -35,11 +33,7 @@ pub struct Signature {
}

#[wasm_bindgen]
pub fn run() -> Promise {
future_to_promise(run_())
}

async fn run_() -> Result<JsValue, JsValue> {
pub async fn run() -> Result<JsValue, JsValue> {
let mut opts = RequestInit::new();
opts.method("GET");
opts.mode(RequestMode::Cors);
Expand Down
74 changes: 73 additions & 1 deletion guide/src/reference/js-promises-and-rust-futures.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,76 @@
# Converting Between JavaScript `Promise`s and Rust `Future`s
# Working with a JS `Promise` and a Rust `Future`

Many APIs on the web work with a `Promise`, such as an `async` function in JS.
Naturally you'll probably want to interoperate with them from Rust! To do that
you can use the `wasm-bindgen-futures` crate as well as Rust `async`
functions.

The first thing you might encounter is the need for working with a `Promise`.
For this you'll want to use [`js_sys::Promise`]. Once you've got one of those
values you can convert that value to `wasm_bindgen_futures::JsFuture`. This type
implements the `std::future::Future` trait which allows naturally using it in an
`async` function. For example:

[`js_sys::Promise`]: https://docs.rs/js-sys/*/js_sys/struct.Promise.html

```rust
async fn get_from_js() -> Result<JsValue, JsValue> {
let promise = js_sys::Promise::resolved(&42.into());
let result = wasm_bindgen_futures::JsFuture::from(promise).await?;
Ok(result)
}
```

Here we can see how converting a `Promise` to Rust creates a `impl Future<Output
= Result<JsValue, JsValue>>`. This corresponds to `then` and `catch` in JS where
a successful promise becomes `Ok` and an erroneous promise becomes `Err`.

Next up you'll probably want to export a Rust function to JS that returns a
promise. To do this you can use an `async` function and `#[wasm_bindgen]`:

```rust
#[wasm_bindgen]
pub async fn foo() {
// ...
}
```

When invoked from JS the `foo` function here will return a `Promise`, so you can
import this as:

```js
import { foo } from "my-module";

async function shim() {
const result = await foo();
// ...
}
```

## Return values of `async fn`

When using an `async fn` in Rust and exporting it to JS there's some
restrictions on the return type. The return value of an exported Rust function
will eventually become `Result<JsValue, JsValue>` where `Ok` turns into a
successfully resolved promise and `Err` is equivalent to throwing an exception.

The following types are supported as return types from an `async fn`:

* `()` - turns into a successful `undefined` in JS
* `T: Into<JsValue>` - turns into a successful JS value
* `Result<(), E: Into<JsValue>>` - if `Ok(())` turns into a successful
`undefined` and otherwise turns into a failed promise with `E` converted to a
JS value
* `Result<T: Into<JsValue>, E: Into<JsValue>>` - like the previous case except
both data payloads are converted into a `JsValue`.

Note that many types implement being converted into a `JsValue`, such as all
imported types via `#[wasm_bindgen]` (aka those in `js-sys` or `web-sys`),
primitives like `u32`, and all exported `#[wasm_bindgen]` types. In general,
you should be able to write code without having too many explicit conversions,
and the macro should take care of the rest!

## Using `wasm-bindgen-futures`

The `wasm-bindgen-futures` crate bridges the gap between JavaScript `Promise`s
and Rust `Future`s. Its `JsFuture` type provides conversion from a JavaScript
Expand Down
40 changes: 40 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,8 @@ pub fn function_table() -> JsValue {
pub mod __rt {
use core::cell::{Cell, UnsafeCell};
use core::ops::{Deref, DerefMut};
use crate::JsValue;

pub extern crate core;
#[cfg(feature = "std")]
pub extern crate std;
Expand Down Expand Up @@ -1095,6 +1097,44 @@ pub mod __rt {
return ret;
}
}

/// An internal helper trait for usage in `#[wasm_bindgen]` on `async`
/// functions to convert the return value of the function to
/// `Result<JsValue, JsValue>` which is what we'll return to JS (where an
/// error is a failed future).
pub trait IntoJsResult {
fn into_js_result(self) -> Result<JsValue, JsValue>;
}

impl IntoJsResult for () {
fn into_js_result(self) -> Result<JsValue, JsValue> {
Ok(JsValue::undefined())
}
}

impl<T: Into<JsValue>> IntoJsResult for T {
fn into_js_result(self) -> Result<JsValue, JsValue> {
Ok(self.into())
}
}

impl<T: Into<JsValue>, E: Into<JsValue>> IntoJsResult for Result<T, E> {
fn into_js_result(self) -> Result<JsValue, JsValue> {
match self {
Ok(e) => Ok(e.into()),
Err(e) => Err(e.into()),
}
}
}

impl<E: Into<JsValue>> IntoJsResult for Result<(), E> {
fn into_js_result(self) -> Result<JsValue, JsValue> {
match self {
Ok(()) => Ok(JsValue::undefined()),
Err(e) => Err(e.into()),
}
}
}
}

/// A wrapper type around slices and vectors for binding the `Uint8ClampedArray`
Expand Down

0 comments on commit e83ca22

Please sign in to comment.