将 Rust 闭包传递给导入的 JavaScript 函数

#[wasm_bindgen] 属性支持将 Rust 闭包传递给 JavaScript,有两种变体

  1. 栈生命周期闭包,在导入的 JavaScript 函数返回后,不应该再由 JavaScript 调用。

  2. 堆分配闭包,可以被调用任意次数,但必须在完成时显式释放。

栈生命周期闭包

具有栈生命周期的闭包被传递给 JavaScript,作为 &dyn Fn&mut dyn FnMut 特性对象


# #![allow(unused_variables)]
#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_variables)]
#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 支持 FnFnMut 闭包,以及参数和返回值。


# #![allow(unused_variables)]
#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"))
}
#}