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

How do I keep internal state in a WebAssembly module written in Rust?

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

在 C 中,类似这样的方法有效:

#include <emscripten/emscripten.h>

int state = 0;

void EMSCRIPTEN_KEEPALIVE inc() {
    state++;
}

int EMSCRIPTEN_KEEPALIVE get() {
    return state;
}

在 Rust 中可以做同样的事情吗?我试着用这样的 static 来做:

static mut state: i32 = 0;

pub fn main() {}

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

#[no_mangle]
pub fn get() -> i32 {
    state
}

但似乎static变量不能可变。

error[E0133]: use of mutable static requires unsafe function or block

一般来说,访问可变全局变量是不安全,这意味着你只能在unsafe块中进行。使用可变全局变量,很容易意外创建悬挂引用(想想对全局可变项的引用 Vec),数据竞争(如果你有多个线程——Rust 不关心你不关心实际上使用线程)或以其他方式调用 undefined behavior.

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


下面是分配唯一状态并对其进行修改的示例:

type State = i32;

#[no_mangle]
pub extern fn new() -> *mut State {
    Box::into_raw(Box::new(0))
}

#[no_mangle]
pub extern fn free(state: *mut State) {
    unsafe { Box::from_raw(state) };
}

#[no_mangle]
pub extern fn add(state: *mut State) {
    unsafe { *state += 1 };
}

#[no_mangle]
pub extern fn get(state: *mut State) -> i32 {
    unsafe { *state }
}
const fs = require('fs-extra');

fs.readFile(__dirname + '/target/wasm32-unknown-unknown/release/state.wasm')
  .then(bytes => WebAssembly.instantiate(bytes))
  .then(({ module, instance }) => {
    const { new: newFn, free, get, add } = instance.exports;

    const state1 = newFn();
    const state2 = newFn();

    add(state1);
    add(state2);
    add(state1);

    console.log(get(state1));
    console.log(get(state2));

    free(state1);
    free(state2);
});
2
1

注意 — 目前需要在发布模式下编译才能工作。调试模式目前有一些问题。

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

全局变量通常会使您的代码变得更糟,您应该避免使用它们。

然而,对于今天特定 WebAssembly 案例,我们不必担心这个问题:

if you have multiple threads

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

// 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 }
}

我们可以看到这个 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());
});
0
2