【问题标题】:How do I keep internal state in a WebAssembly module written in Rust?如何在用 Rust 编写的 WebAssembly 模块中保持内部状态?
【发布时间】:2017-12-08 21:13:17
【问题描述】:

我想对我的网络应用程序的每一帧的大量数据进行计算。 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 变量不能是可变的。

【问题讨论】:

  • 但静态变量似乎不能是可变的。 - 你为什么这么说?错误消息明确告诉你如何使用它们:“使用可变静态需要不安全的函数或块”。现在,这是否是一个好主意是一个不同的问题......
  • 这是blog post of someone who successfully used lazy_static! for this purpose。请注意,他们需要将其包装在 Mutex 中以使编译器满意,因此我不知道性能影响是什么。而且他们没有使用 emscripten,而是使用了 wasm32-unknown-unknown 编译器目标。

标签: rust webassembly


【解决方案1】:

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

然而,对于 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 }
}

我们可以看到这个 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

【讨论】:

  • 感谢 Shepmaster。我不知道 unsafe 关键字(我是 Rust 初学者)
  • @JakobMulvadNielsen 错误消息明确告知您不安全:“使用可变静态需要不安全的函数或块”。如果您不阅读编译器错误消息,您将在使用 Rust 时非常糟糕
【解决方案2】:
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 不安全的,因为您正在传递原始指针,但它在调用代码中更清楚地表明存在一些可变状态正在被操纵。另请注意,调用者现在有责任确保正确处理状态指针。

【讨论】:

  • 查看我的Rust FFI Omnibus,它讨论了将这种类型的 FFI 包装在一些更好的结构中的一些方法。
猜你喜欢
  • 2014-02-15
  • 1970-01-01
  • 2021-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-12
相关资源
最近更新 更多