如何在用 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
我想在我的网络应用程序的每一帧上对大量数据进行计算。 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