mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-28 20:43:23 +03:00
guide: Clean up passing rust closures to JS section; add passing JS closures to rust section
This commit is contained in:
parent
33520d4828
commit
e22ccb4d5d
@ -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)
|
||||
|
@ -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
|
118
guide/src/reference/passing-rust-closures-to-js.md
Normal file
118
guide/src/reference/passing-rust-closures-to-js.md
Normal 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"));
|
||||
}
|
||||
```
|
31
guide/src/reference/receiving-js-closures-in-rust.md
Normal file
31
guide/src/reference/receiving-js-closures-in-rust.md
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
Loading…
Reference in New Issue
Block a user