如何通过 WebAssembly return 将 Rust 闭包 JavaScript?

How to return a Rust closure to JavaScript via WebAssembly?

closure.rs 上的评论非常好,但是我不能让它工作 return从 WebAssembly 库中关闭闭包。

我有这样的功能:

#[wasm_bindgen]
pub fn start_game(
    start_time: f64,
    screen_width: f32,
    screen_height: f32,
    on_render: &js_sys::Function,
    on_collision: &js_sys::Function,
) -> ClosureTypeHere {
    // ...
}

在该函数中我做了一个闭包,假设 Closure::wrap 是拼图的一部分,并从 closure.rs 复制:

let cb = Closure::wrap(Box::new(move |time| time * 4.2) as Box<FnMut(f64) -> f64>);

如何 return 来自 start_game 的回调以及 ClosureTypeHere 应该是什么?

这个想法是 start_game 将创建本地可变对象——比如相机,JavaScript 端应该能够调用函数 Rust returns 以更新那个相机。

据我从文档中了解到,它不应该导出 Rust 闭包,它们只能作为参数传递给导入的 JS 函数,但这一切都发生在 Rust 代码中。

https://rustwasm.github.io/wasm-bindgen/reference/passing-rust-closures-to-js.html#passing-rust-closures-to-imported-javascript-functions

我做了几个实验,当 Rust 函数 returns 提到的 'Closure' 类型时,编译器抛出异常:the trait wasm_bindgen::convert::IntoWasmAbi is not implemented for wasm_bindgen::prelude::Closure<(dyn std::ops::FnMut() -> u32 + 'static)>

在所有示例中,闭包都被包装到任意结构中,但之后您就不能在 JS 端调用它了。

这是一个很好的问题,也有一些细微差别! closures example in the wasm-bindgen guide (and the section about passing closures to JavaScript) 也值得一提,如有必要,也可以对此做出贡献!

不过,为了让您开始,您可以这样做:

use wasm_bindgen::{Closure, JsValue};

#[wasm_bindgen]
pub fn start_game(
    start_time: f64,
    screen_width: f32,
    screen_height: f32,
    on_render: &js_sys::Function,
    on_collision: &js_sys::Function,
) -> JsValue {
    let cb = Closure::wrap(Box::new(move |time| {
        time * 4.2
    }) as Box<FnMut(f64) -> f64>);

    // Extract the `JsValue` from this `Closure`, the handle
    // on a JS function representing the closure
    let ret = cb.as_ref().clone();

    // Once `cb` is dropped it'll "neuter" the closure and
    // cause invocations to throw a JS exception. Memory
    // management here will come later, so just leak it
    // for now.
    cb.forget();

    return ret;
}

在 return 值之上只是一个普通的 JS 对象(这里是 JsValue),我们使用您已经看到的 Closure 类型创建它。这将允许您快速 return 关闭 JS,您也可以从 JS 调用它。

你也问过存储可变对象之类的,这些都可以通过正常的 Rust 闭包、捕获等来完成。例如上面 FnMut(f64) -> f64 的声明是 JS 函数的签名,如果您真的需要,它可以是任何类型集,例如 FnMut(String, MyCustomWasmBindgenType, f64) -> Vec<u8>。要捕获本地对象,您可以执行以下操作:

let mut camera = Camera::new();
let mut state = State::new();
let cb = Closure::wrap(Box::new(move |arg1, arg2| { // note the `move`
    if arg1 {
        camera.update(&arg2);
    } else {
        state.update(&arg2);
    }
}) as Box<_>);

(或类似的东西)

此处 camerastate 变量将由闭包拥有并同时删除。关于闭包的更多信息 can be found in the Rust book.

这里还值得简要介绍一下内存管理方面。在里面 上面的示例我们正在调用 forget() 这会泄漏内存,如果多次调用 Rust 函数可能会成为问题(因为它会泄漏大量内存)。这里的根本问题是在创建的 JS 函数对象引用的 WASM 堆上分配了内存。理论上,每当 JS 函数对象被 GC 时,都需要释放分配的内存,但我们无法知道何时发生(直到 WeakRef exists!)。

与此同时,我们选择了替代策略。相关内存是 每当 Closure 类型本身被删除时释放,提供 确定性破坏。然而,这使得工作变得困难,因为我们需要手动确定何时删除 Closure。如果 forget 不适用于您的用例,则删除 Closure 的一些想法是:

  • 首先,如果是只调用一次的JS闭包,那么可以使用Rc/RefCellClosure 放在闭包本身内(使用一些内部 可变性恶作剧)。我们最终也应该 提供 本地支持 FnOncewasm-bindgen 中也是如此!

  • 接下来,你可以return一个Rust的辅助JS对象,它有一个手册free 方法。例如 #[wasm_bindgen]-注释包装器。这个包装器会 然后需要在合适的时候在JS中手动释放

如果你能过得去,forget 是迄今为止最简单的事情 现在,但这绝对是一个痛点!我们等不及 WeakRef 的存在了:)