美文网首页
使用 Rust 编写 WebAssembly

使用 Rust 编写 WebAssembly

作者: 舌尖上的大胖 | 来源:发表于2023-10-24 12:37 被阅读0次

    参考:《编译 Rust 为 WebAssembly》
    https://developer.mozilla.org/zh-CN/docs/WebAssembly/Rust_to_Wasm

    一、环境搭建

    (一)安装工具链

    1、wasm-bindgen

    Facilitating high-level interactions between Wasm modules and JavaScript
    https://github.com/rustwasm/wasm-bindgen

    # 使用 Cargo 安装
    cargo install wasm-bindgen-cli
    

    文档:
    Introduction - The wasm-bindgen Guide
    https://rustwasm.github.io/wasm-bindgen/introduction.html
    https://rustwasm.github.io/

    介绍- The wasm-bindgen Guide
    https://llever.com/wasm-bindgen/

    2、wasm-pack

    Your favorite rust -> wasm workflow tool!
    https://github.com/rustwasm/wasm-pack

    # 使用 Cargo 安装
    cargo install wasm-pack
    # 使用 Homebrew 安装
    brew install wasm-pack
    
    3、wasm-tools (可选)

    Low level tooling for WebAssembly in Rust
    https://github.com/bytecodealliance/wasm-tools

    # 使用 Cargo 安装
    cargo install wasm-tools
    # 使用 Homebrew 安装
    brew install wasm-tools
    
    4、binaryen (包含 wasm-opt,可选)

    Optimizer and compiler/toolchain library for WebAssembly
    https://github.com/WebAssembly/binaryen

    # 使用 Homebrew 安装
    brew install binaryen
    # 通过 Cargo 安装 Rust 版 wasm-opt
    cargo install wasm-opt
    

    (二)添加平台支持

    增加对 WebAssembly 的支持:

    # 查看当前 Rust 环境支持构建的平台
    rustup target list
    # 添加 WebAssembly 的支持
    rustup target add wasm32-unknown-unknown
    

    二、创建工程

    (一)手工创建

    1、创建 Rust 库工程
    cargo new --lib hello-wasm
    
    2、编写代码

    清理掉 src/lib.rs 中的内容,然后写入以下内容:

    extern crate wasm_bindgen;
    
    use wasm_bindgen::prelude::*;
    
    #[wasm_bindgen]
    extern {
        pub fn alert(s: &str);
    }
    
    #[wasm_bindgen]
    pub fn greet(name: &str) {
        alert(&format!("Hello, {}!", name));
    }
    
    • 通过 #[wasm_bindgen] 来指定与外部 JavaScript 进行交互。
      • extern 用于通过 Rust 调用外部的 JavaScript 代码
    #[wasm_bindgen]
    extern {
        pub fn alert(s: &str);
    }
    
    • 供外部 JavaScript 调用的 Rust 代码
    #[wasm_bindgen]
    pub fn greet(name: &str) {
        alert(&format!("Hello, {}!", name));
    }
    
    3、配置 Cargo.toml
    [package]
    name = "hello-wasm"
    version = "0.1.0"
    authors = ["Your Name <you@example.com>"]
    description = "A sample project with wasm-pack"
    license = "MIT/Apache-2.0"
    repository = "https://github.com/yourgithubusername/hello-wasm"
    
    [lib]
    crate-type = ["cdylib"]
    
    [dependencies]
    wasm-bindgen = "0.2"
    

    (二)使用 wasm-pack 工具创建

    创建工程

    wasm-pack new
    

    三、构建 WebAssembly

    (一)手工构建

    # 使用 Cargo 构建
    cargo build --target wasm32-unknown-unknown --release
    

    构建结果在这里:

    <工程目录>/target/wasm32-unknown-unknown/release/<工程名>.wasm
    

    (二)使用 wasm-pack 工具构建

    构建工程

    wasm-pack build -t web
    

    构建结果在这里:

    <工程名目录>/pkg/
    

    四、踩坑

    (一).wasm 文件与 JavaScript 的关系

    从本质上说,JavaScript 可以通过 Ajax 将 .wasm 文件加载为二进制流,然后调用 WebAssembly 对象加载这个二进制流,从而完成对 .wasm 文件的加载,并完成初始化。这时已经可以通过 JavaScript 调用 .wasm 文件中的方法了。

    如果要在 Rust 中支持这种方式,那么需要按如下方式编写函数:

    // 使用 #[no_mangle] 防止名称修饰,使函数名可与其他语言的接口匹配
    #[no_mangle]
    pub extern "C" fn add(a: i32, b: i32) -> i32 {
        a + b
    }
    

    而且这种方式不需要什么三方依赖。通过 rustup target list 命令查看支持 wasm32-unknown-unknown 这个 target 就可以。
    几种方式示例:https://github.com/Herbert8/simple-webassembly-demo/blob/main/web/utils.js

    这种直接编写直接加载的方式,直观简单。缺点是对于复杂数据类型(如:字符串)的操作比较繁琐,需要自己在 JavaScript 中增加很多额外处理。这样的话,在组建复杂度比较高的场景,编写的 WebAssembly 难以作为成形的组件使用。
    所以一般使用 Rust 编写的 WebAssembly 组件,都是结合着前端框架一起提供,便于使用。这也就是为什么 Rust 开发 WebAssembly 的教程都会涉及到 npm(这是很好的做法,但并不是必需的)。

    (二)工具链在开发过程中的作用

    根据前面的描述,在 WebAssembly 开发的过程中,除了业务代码,基础框架(包括 Rust 和 JavaScript)的工作多且繁琐,靠人工创建工程的方式效率太低,这个问题可以引入 wasm-pack 这个工具来解决。对于用于生产的系统,或者复杂的场景,不建议手工打造了,还是采用 wasm-pack,能帮我们做以下事情:
    1、创建工程

    wasm-pack new my_proj
    

    会根据一个 WebAssembly 的工程模板进行工程创建,包括基础代码和配置,省去手工配置的繁琐。

    2、工程的构建

    wasm-pack build -t web
    # 或
    wasm-pack build --target web
    

    这里指定 target 非常重要!!!因为在 build 的时候,除了生成 WebAssembly,还会生成配套的 JavaScript 包装,以便于外部调用。当没有明确指定 target 时会使用 bundler,这种方式不适合 Web,使用时报错。

    3、wasm-pack 也会与前面提及的其他工具链集成,同时完成代码优化、瘦身以及包装文件的生成。

    (三)包装文件的使用

    使用 wasm-pack 构建后,除了 .wasm 文件,还会生成其对应的 JavaScript 包装文件。这里说下包装文件的使用规则。
    1、包装文件使用了 ES6 模块机制
    2、包装文件默认导出的是它的初始化器
    3、需要在初始化完成后,再调用自己的方法

    // 导入 js 中提供的函数
    import init, { my_func1 as myFunc1, my_func2 as myFunc2 } from 'my_proj.js'
    // 初始化(异步执行,通过 await 等待,必要时做好相关异常处理)
    await init()
    myFunc1()
    myFunc2()
    

    (四)开发工作流的优化

    采用 VS Code 进行开发是不错的选择,能编写 Rust、配置文件以及前端代码。但每次修改后编译、将 .wasm 及包装文件部署到 Web 服务器,修改前端代码后也需要部署,还是有些繁琐。
    这里推荐使用 make,自行编写 Makefile,VS Code 内置了 Makefile 的支持。可以根据自己的需要编写 Makefile,根据自己要做的事情定义 Target,配合 VS Code 自定义快捷键的功能,感觉整个开发工作流没那么繁琐了。
    大家有什么好的建议请回复。

    五、遗留问题

    Debug 方面没找到好办法,还在研究中。有了新发现随时补充上来。

    (完)

    相关文章

      网友评论

          本文标题:使用 Rust 编写 WebAssembly

          本文链接:https://www.haomeiwen.com/subject/tyrsidtx.html