如何在用 Rust 编写的 WebAssembly 模块中保持内部状态?

Jak*_*sen 7 rust webassembly

我想对网络应用程序的每一帧的大量数据进行计算。JavaScript 只会使用其中的一个子集,因此与其在 WebAssembly 和 JavaScript 之间来回发送每一帧的整个数据集,不如在我的 WebAssembly 模块内部维护数据。

在 C 中,类似这样的工作:

#include <emscripten/emscripten.h>

int state = 0;

void EMSCRIPTEN_KEEPALIVE inc() {
    state++;
}

int EMSCRIPTEN_KEEPALIVE get() {
    return state;
}
Run Code Online (Sandbox Code Playgroud)

在 Rust 中可以做同样的事情吗?我尝试这样做static

static mut state: i32 = 0;

pub fn main() {}

#[no_mangle]
pub fn add() {
    state += 1;
}

#[no_mangle]
pub fn get() -> i32 {
    state
}
Run Code Online (Sandbox Code Playgroud)

但似乎static变量不能是可变的。

She*_*ter 6

Francis Gagné 是绝对正确的,全局变量通常会使您的代码变得更糟,您应该避免使用它们。

但是,对于今天的 WebAssembly的具体情况,我们不必担心这个问题:

如果你有多个线程

因此,如果我们有充分的理由这样做,我们可以选择使用可变静态变量:

// Only valid because we are using this in a WebAssembly
// context without threads.
static mut STATE: i32 = 0;

#[no_mangle]
pub extern fn add() {
    unsafe { STATE += 1 };
}

#[no_mangle]
pub extern fn get() -> i32 {
    unsafe { STATE }
}
Run Code Online (Sandbox Code Playgroud)

我们可以看到这个 NodeJS 驱动程序的行为:

const fs = require('fs-extra');

fs.readFile(__dirname + '/target/wasm32-unknown-unknown/release/state.wasm')
  .then(bytes => WebAssembly.instantiate(bytes))
  .then(({ module, instance }) => {
    const { get, add } = instance.exports;
    console.log(get());
    add();
    add();
    console.log(get());
});
Run Code Online (Sandbox Code Playgroud)
// Only valid because we are using this in a WebAssembly
// context without threads.
static mut STATE: i32 = 0;

#[no_mangle]
pub extern fn add() {
    unsafe { STATE += 1 };
}

#[no_mangle]
pub extern fn get() -> i32 {
    unsafe { STATE }
}
Run Code Online (Sandbox Code Playgroud)

  • @JakobMulvadNielsen 错误消息明确告知您不安全:“使用可变静态需要不安全的函数或块”。如果您不阅读编译器错误消息,您将在使用 Rust 时度过*非常糟糕的时光*。 (2认同)

Fra*_*gné 5

\n
error[E0133]: use of mutable static requires unsafe function or block\n
Run Code Online (Sandbox Code Playgroud)\n
\n\n

一般来说,访问可变全局变量是不安全的,这意味着您只能在unsafe块中执行此操作。使用可变的全局变量,很容易意外地创建悬空引用(想象一下对全局可变的项的引用Vec)、数据争用(如果你有多个线程——Rust 不在乎你是否不这样做)实际上使用线程)或以其他方式调用未定义的行为

\n\n

全局变量通常不是问题的最佳解决方案,因为它会降低软件的灵活性和可重用性。相反,请考虑将状态显式传递(通过引用,因此您不需要复制它)到需要对其进行操作的函数。这使得调用代码可以处理多个独立状态。

\n\n
\n\n

这是分配唯一状态并修改它的示例:

\n\n
type State = i32;\n\n#[no_mangle]\npub extern fn new() -> *mut State {\n    Box::into_raw(Box::new(0))\n}\n\n#[no_mangle]\npub extern fn free(state: *mut State) {\n    unsafe { Box::from_raw(state) };\n}\n\n#[no_mangle]\npub extern fn add(state: *mut State) {\n    unsafe { *state += 1 };\n}\n\n#[no_mangle]\npub extern fn get(state: *mut State) -> i32 {\n    unsafe { *state }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
const fs = require(\'fs-extra\');\n\nfs.readFile(__dirname + \'/target/wasm32-unknown-unknown/release/state.wasm\')\n  .then(bytes => WebAssembly.instantiate(bytes))\n  .then(({ module, instance }) => {\n    const { new: newFn, free, get, add } = instance.exports;\n\n    const state1 = newFn();\n    const state2 = newFn();\n\n    add(state1);\n    add(state2);\n    add(state1);\n\n    console.log(get(state1));\n    console.log(get(state2));\n\n    free(state1);\n    free(state2);\n});\n
Run Code Online (Sandbox Code Playgroud)\n\n
error[E0133]: use of mutable static requires unsafe function or block\n
Run Code Online (Sandbox Code Playgroud)\n\n

注意\xe2\x80\x94 目前需要在发布模式下编译才能工作。调试模式目前存在一些问题。

\n\n

诚然,这并不是不安全,因为您正在传递原始指针,但它使调用代码中更清楚地表明存在一些正在操纵的可变状态。另请注意,现在调用者有责任确保正确处理状态指针。

\n