From e22ccb4d5dd211fd7e414bd9e30f942dac455207 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 6 Aug 2018 17:43:36 -0700 Subject: [PATCH] guide: Clean up passing rust closures to JS section; add passing JS closures to rust section --- guide/src/SUMMARY.md | 3 +- guide/src/reference/closures.md | 65 ---------- .../reference/passing-rust-closures-to-js.md | 118 ++++++++++++++++++ .../receiving-js-closures-in-rust.md | 31 +++++ 4 files changed, 151 insertions(+), 66 deletions(-) delete mode 100644 guide/src/reference/closures.md create mode 100644 guide/src/reference/passing-rust-closures-to-js.md create mode 100644 guide/src/reference/receiving-js-closures-in-rust.md diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 0abd4782f..84ec98d62 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -9,7 +9,8 @@ - [What Just Happened?](./whirlwind-tour/what-just-happened.md) - [What Else Can We Do?](./whirlwind-tour/what-else-can-we-do.md) - [Reference](./reference/index.md) - - [Closures](./reference/closures.md) + - [Passing Rust Closures to JS](./reference/passing-rust-closures-to-js.md) + - [Receiving JS Closures in Rust](./reference/receiving-js-closures-in-rust.md) - [No ES Modules](./reference/no-esm.md) - [Passing Arbitrary data](./reference/passing-data.md) - [Feature Reference](./reference/feature-reference.md) diff --git a/guide/src/reference/closures.md b/guide/src/reference/closures.md deleted file mode 100644 index 1b9215054..000000000 --- a/guide/src/reference/closures.md +++ /dev/null @@ -1,65 +0,0 @@ -# Closures - -The `#[wasm_bindgen]` attribute supports some Rust closures being passed to JS. -Examples of what you can do are: - -```rust -#[wasm_bindgen] -extern { - fn foo(a: &Fn()); // could also be `&mut FnMut()` -} -``` - -Here a function `foo` is imported from JS where the first argument is a *stack -closure*. You can call this function with a `&Fn()` argument and JS will receive -a JS function. When the `foo` function returns, however, the JS function will be -invalidated and any future usage of it will raise an exception. - -Closures also support arguments and return values like exports do, for example: - -```rust -#[wasm_bindgen] -extern { - type Foo; - - fn bar(a: &Fn(u32, String) -> Foo); -} -``` - -Sometimes the stack behavior of these closures is not desired. For example you'd -like to schedule a closure to be run on the next turn of the event loop in JS -through `setTimeout`. For this you want the imported function to return but the -JS closure still needs to be valid! - -To support this use case you can do: - -```rust -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -extern { - fn baz(a: &Closure); -} -``` - -The `Closure` type is defined in the `wasm_bindgen` crate and represents a "long -lived" closure. The JS closure passed to `baz` is still valid after `baz` -returns, and the validity of the JS closure is tied to the lifetime of the -`Closure` in Rust. Once `Closure` is dropped it will deallocate its internal -memory and invalidate the corresponding JS function. - -Like stack closures a `Closure` also supports `FnMut`: - -```rust -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -extern { - fn another(a: &Closure u32>); -} -``` - -At this time you cannot [pass a JS closure to Rust][cbjs], you can only pass a -Rust closure to JS in limited circumstances. - -[cbjs]: https://github.com/rustwasm/wasm-bindgen/issues/103 diff --git a/guide/src/reference/passing-rust-closures-to-js.md b/guide/src/reference/passing-rust-closures-to-js.md new file mode 100644 index 000000000..fc09fce4d --- /dev/null +++ b/guide/src/reference/passing-rust-closures-to-js.md @@ -0,0 +1,118 @@ +# Passing Rust Closures to Imported JavaScript Functions + +The `#[wasm_bindgen]` attribute supports Rust closures being passed to +JavaScript in two variants: + +1. Stack-lifetime closures that should not be invoked by JavaScript again after + the imported JavaScript function that the closure was passed to returns. + +2. Heap-allocated closures that can be invoked any number of times, but must be + explicitly deallocated when finished. + +## Stack-Lifetime Closures + +Closures with a stack lifetime are passed to JavaScript as either `&Fn` or `&mut +FnMut` trait objects: + +```rust +// Import JS functions that take closures + +#[wasm_bindgen] +extern { + fn takes_immutable_closure(f: &Fn()); + + fn takes_mutable_closure(f: &mut FnMut()); +} + +// Usage + +takes_immutable_closure(&|| { + // ... +}); + +let mut times_called = 0; +takes_mutable_closure(&mut || { + times_called += 1; +}); +``` + +**Once these imported functions return, the closures that were given to them +will become invalidated, and any future attempts to call those closures from +JavaScript will raise an exception.** + +Closures also support arguments and return values like exports do, for example: + +```rust +#[wasm_bindgen] +extern { + fn takes_closure_that_takes_int_and_returns_string(x: &Fn(u32) -> String); +} + +takes_closure_that_takes_int_and_returns_string(&|x: u32| -> String { + format!("x is {}", x) +}); +``` + +## Heap-Allocated Closures + +Sometimes the discipline of stack-lifetime closures is not desired. For example, +you'd like to schedule a closure to be run on the next turn of the event loop in +JavaScript through `setTimeout`. For this, you want the imported function to +return but the JavaScript closure still needs to be valid! + +For this scenario, you need the `Closure` type, which is defined in the +`wasm_bindgen` crate, exported in `wasm_bindgen::prelude`, and represents a +"long lived" closure. + +The validity of the JavaScript closure is tied to the lifetime of the `Closure` +in Rust. **Once a `Closure` is dropped, it will deallocate its internal memory +and invalidate the corresponding JavaScript function so that any further +attempts to invoke it raise an exception.** + +Like stack closures a `Closure` supports both `Fn` and `FnMut` closures, as well +as arguments and returns. + +```rust +#[wasm_bindgen] +extern { + fn setInterval(closure: &Closure, millis: u32) -> f64; + fn cancelInterval(token: f64); + + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); +} + +#[wasm_bindgen] +pub struct Interval { + closure: Closure, + token: f64, +} + +impl Interval { + pub fn new(millis: u32, f: F) -> Interval + where + F: FnMut() + { + // Construct a new closure. + let closure = Closure::new(f); + + // Pass the closuer to JS, to run every n milliseconds. + let token = setInterval(&closure, millis); + + Interval { closure, token } + } +} + +// When the Interval is destroyed, cancel its `setInterval` timer. +impl Drop for Interval { + fn drop(&mut self) { + cancelInterval(self.token); + } +} + +// Keep logging "hello" every second until the resulting `Interval` is dropped. +#[wasm_bindgen] +pub fn hello() -> Interval { + Interval::new(1_000, || log("hello")); +} +``` diff --git a/guide/src/reference/receiving-js-closures-in-rust.md b/guide/src/reference/receiving-js-closures-in-rust.md new file mode 100644 index 000000000..6d78d5ec2 --- /dev/null +++ b/guide/src/reference/receiving-js-closures-in-rust.md @@ -0,0 +1,31 @@ +# Receiving JavaScript Closures in Exported Rust Functions + +You can use the `js-sys` crate to access JavaScript's `Function` type, and +invoke that function via `Function.prototype.apply` and +`Function.prototype.call`. + +For example, we can wrap a `Vec` in a new type, export it to JavaScript, +and invoke a JavaScript closure on each member of the `Vec`: + +```rust +extern crate js_sys; +extern crate wasm_bindgen; + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct VecU32 { + xs: Vec, +} + +#[wasm_bindgen] +impl VecU32 { + pub fn each(&self, f: &js_sys::Function) { + let this = JsValue::NULL; + for x in &self.xs { + let x = JsValue::from(x); + let _ = f.call1(&this, &x); + } + } +} +```