mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-22 09:11:35 +00:00
guide: Clean up passing rust closures to JS section; add passing JS closures to rust section
This commit is contained in:
@ -9,7 +9,8 @@
|
|||||||
- [What Just Happened?](./whirlwind-tour/what-just-happened.md)
|
- [What Just Happened?](./whirlwind-tour/what-just-happened.md)
|
||||||
- [What Else Can We Do?](./whirlwind-tour/what-else-can-we-do.md)
|
- [What Else Can We Do?](./whirlwind-tour/what-else-can-we-do.md)
|
||||||
- [Reference](./reference/index.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)
|
- [No ES Modules](./reference/no-esm.md)
|
||||||
- [Passing Arbitrary data](./reference/passing-data.md)
|
- [Passing Arbitrary data](./reference/passing-data.md)
|
||||||
- [Feature Reference](./reference/feature-reference.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
Reference in New Issue
Block a user