代码之家  ›  专栏  ›  技术社区  ›  Jakob Mulvad Nielsen

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

  •  9
  • Jakob Mulvad Nielsen  · 技术社区  · 7 年前

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

    在C语言中,类似这样的操作是可行的:

    #include <emscripten/emscripten.h>
    
    int state = 0;
    
    void EMSCRIPTEN_KEEPALIVE inc() {
        state++;
    }
    
    int EMSCRIPTEN_KEEPALIVE get() {
        return state;
    }
    

    生锈也可能发生同样的情况吗?我试着用 static 这样地:

    static mut state: i32 = 0;
    
    pub fn main() {}
    
    #[no_mangle]
    pub fn add() {
        state += 1;
    }
    
    #[no_mangle]
    pub fn get() -> i32 {
        state
    }
    

    但是看起来 静止的 变量不能是可变的。

    2 回复  |  直到 7 年前
        1
  •  7
  •   Shepmaster Tim Diekmann    7 年前

    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
    
        2
  •  5
  •   Shepmaster Tim Diekmann    7 年前
    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
    

    笔记 此当前 需要 在发布模式下编译以工作。调试模式目前存在一些问题。

    诚然,这不是 较少的 不安全,因为您正在传递原始指针,但它使调用代码中的某些可变状态被操纵变得更清楚。还请注意,现在由 呼叫者 以确保正确处理状态指针。