將 Rust 閉包傳遞給匯入的 JavaScript 函式

`#[wasm_bindgen]` 屬性支援以兩種變體將 Rust 閉包傳遞給 JavaScript

  1. 堆疊生命週期的閉包,在將閉包傳遞給的匯入 JavaScript 函式返回後,不應再次由 JavaScript 呼叫。

  2. 堆積分配的閉包,可以呼叫任意次數,但完成後必須明確釋放。

堆疊生命週期閉包

具有堆疊生命週期的閉包會作為 `&dyn Fn` 或 `&mut dyn FnMut` 特徵物件傳遞給 JavaScript

#![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)
});
}

堆積分配閉包

有時不希望使用堆疊生命週期閉包的規範。例如,您想要排程一個閉包,使其透過 `setTimeout` 在 JavaScript 的下一個事件迴圈中執行。為此,您希望匯入的函式返回,但 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"))
}
}