将 Rust 闭包传递给导入的 JavaScript 函数
#[wasm_bindgen]
属性支持将 Rust 闭包传递给 JavaScript,有两种变体
-
栈生命周期闭包,在导入的 JavaScript 函数返回后,不应该再由 JavaScript 调用。
-
堆分配闭包,可以被调用任意次数,但必须在完成时显式释放。
栈生命周期闭包
具有栈生命周期的闭包被传递给 JavaScript,作为 &dyn Fn
或 &mut dyn FnMut
特性对象。
#![allow(unused)] fn main() { // Import JS functions that take closures #[wasm_bindgen] extern "C" { fn takes_immutable_closure(f: &dyn Fn()); fn takes_mutable_closure(f: &mut dyn FnMut()); } // Usage takes_immutable_closure(&|| { // ... }); let mut times_called = 0; takes_mutable_closure(&mut || { times_called += 1; }); }
一旦这些导入的函数返回,传递给它们的闭包将失效,任何未来尝试从 JavaScript 调用这些闭包的行为都会引发异常。
闭包也支持参数和返回值,就像导出一样,例如
#![allow(unused)] fn main() { #[wasm_bindgen] extern "C" { fn takes_closure_that_takes_int_and_returns_string(x: &dyn Fn(u32) -> String); } takes_closure_that_takes_int_and_returns_string(&|x: u32| -> String { format!("x is {}", x) }); }
堆分配闭包
有时不需要栈生命周期闭包的约束。例如,您可能希望通过 JavaScript 的 setTimeout
将闭包安排在事件循环的下一轮运行。为此,您希望导入的函数返回,但 JavaScript 闭包仍然需要有效!
对于这种情况,您需要 Closure
类型,它在 wasm_bindgen
crate 中定义,在 wasm_bindgen::prelude
中导出,并表示一个“长生命周期”闭包。
JavaScript 闭包的有效性与 Rust 中 Closure
的生命周期绑定在一起。一旦 Closure
被丢弃,它将释放其内部内存并使相应的 JavaScript 函数失效,因此任何进一步尝试调用它都会引发异常。
与栈闭包一样,Closure
支持 Fn
和 FnMut
闭包,以及参数和返回值。
#![allow(unused)] fn main() { #[wasm_bindgen] extern "C" { fn setInterval(closure: &Closure<dyn FnMut()>, millis: u32) -> f64; fn clearInterval(token: f64); #[wasm_bindgen(js_namespace = console)] fn log(s: &str); } #[wasm_bindgen] pub struct Interval { closure: Closure<dyn FnMut()>, token: f64, } impl Interval { pub fn new<F: 'static>(millis: u32, f: F) -> Interval where F: FnMut() { // Construct a new closure. let closure = Closure::new(f); // Pass the closure to JS, to run every n milliseconds. let token = setInterval(&closure, millis); Interval { closure, token } } } // When the Interval is destroyed, clear its `setInterval` timer. impl Drop for Interval { fn drop(&mut self) { clearInterval(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")) } }