guide: Clean up passing rust closures to JS section; add passing JS closures to rust section

This commit is contained in:
Nick Fitzgerald 2018-08-06 17:43:36 -07:00
parent 33520d4828
commit e22ccb4d5d
4 changed files with 151 additions and 66 deletions

View File

@ -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)

View File

@ -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<Fn()>);
}
```
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<FnMut() -> 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

View File

@ -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<FnMut()>, millis: u32) -> f64;
fn cancelInterval(token: f64);
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub struct Interval {
closure: Closure<FnMut()>,
token: f64,
}
impl Interval {
pub fn new<F>(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"));
}
```

View File

@ -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<u32>` 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<u32>,
}
#[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);
}
}
}
```