Rust WebAssembly 入门教程

发布于:2025-09-15 ⋅ 阅读:(16) ⋅ 点赞:(0)

WebAssembly(WASM)是一种低级二进制指令格式,为高性能 Web 应用提供了新的可能。Rust 凭借其内存安全特性和零成本抽象,成为编写 WASM 的理想选择。本教程将从零开始,带你逐步掌握 Rust 到 WASM 的开发流程。


准备工作

首先,我们需要安装必要的工具链:

  1. 安装 Rust
    访问rustup.rs,按照指示安装 Rust 工具链。安装完成后,你将拥有cargo(Rust 的包管理器)和rustc(编译器)。

  2. 安装 wasm-pack
    这是一个将 Rust 代码编译为 WASM 并准备好在 Web 上使用的工具:

cargo install wasm-pack

第一个 Rust WASM 项目

让我们创建一个简单的项目,实现一个加法函数并在浏览器中调用它。

1. 创建新项目

wasm-pack new rust-wasm-demo
cd rust-wasm-demo

这个命令会创建一个基本的项目结构,其中包含:

  • Cargo.toml:项目配置文件
  • src/lib.rs:Rust 源代码文件
  • src/utils.rs:一些辅助工具函数

2. 编写 Rust 代码

打开src/lib.rs,替换为以下内容:

use wasm_bindgen::prelude::*;

// 暴露给JavaScript的函数
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// 测试函数
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }
}

#[wasm_bindgen]属性是关键,它允许 Rust 函数被 JavaScript 调用,反之亦然。

3. 编译为 WASM

运行以下命令将 Rust 代码编译为 WebAssembly:

wasm-pack build --target web

这个命令会:

  • 编译 Rust 代码为 WASM
  • 生成 JavaScript 绑定
  • 创建一个pkg目录,包含所有输出文件

编译成功后,pkg目录中会有:

  • rust_wasm_demo_bg.wasm:编译后的 WASM 二进制文件
  • rust_wasm_demo.js:自动生成的 JavaScript 绑定
  • rust_wasm_demo.d.ts:TypeScript 类型定义(可选)

4. 创建网页调用 WASM

在项目根目录创建一个wasmtest.html文件:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Rust WASM Demo</title>
</head>
<body>
    <h1>Rust WASM 加法演示</h1>
    <input type="number" id="num1" value="2"> +
    <input type="number" id="num2" value="3"> =
    <span id="result"></span>
    <button id="calculateBtn">计算</button>

    <script type="module">
        // 导入WASM模块
        import init, { add } from './pkg/rust_wasm_demo.js';
        
        // 初始化WASM
        async function run() {
            await init();
            calculate(); // 初始计算
            
            // 绑定按钮点击事件
            document.getElementById('calculateBtn').addEventListener('click', calculate);
        }
        
        // 计算函数
        function calculate() {
            const num1 = parseInt(document.getElementById('num1').value);
            const num2 = parseInt(document.getElementById('num2').value);
            const result = add(num1, num2); // 调用Rust函数
            document.getElementById('result').textContent = result;
        }
        
        // 启动应用
        run();
    </script>
</body>
</html>
    

5. 运行测试

由于浏览器的安全限制,我们不能直接通过file://协议运行,需要一个本地服务器。你可以使用 任何简单的 HTTP 静态资源服务器,放置以上文件:

pkg/rust_wasm_demo.js
pkg/rust_wasm_demo_bg.wasm
wasmtest.html

通过访问http协议下的wasmtest.html文件,就可以看到和测试效果。

至此,我们完成了在JavaScript中如何调用WASM中的方法。那么接下来,我们反过来,让WASM调用Web API(也就是大家说的让WASM调用Web API)。既然WASM能调用Web API,那么也能调用你自定义的JavaScript函数。

与 DOM 交互

让我们升级示例,展示 Rust 如何与浏览器 DOM 交互。修改src/lib.rs

mod utils;

use wasm_bindgen::prelude::*;

use web_sys::{Document, Window};

// 获取浏览器窗口对象
#[wasm_bindgen]
pub fn get_window() -> Option<Window> {
    web_sys::window()
}

// 获取文档对象
#[wasm_bindgen]
pub fn get_document() -> Option<Document> {
    web_sys::window()?.document()
}

// 创建并添加元素到页面
#[wasm_bindgen]
pub fn add_hello_element() {
    // 安全地获取文档对象
    let document = match get_document() {
        Some(doc) => doc,
        None => return,
    };
    
    // 创建一个新的div元素
    let div = match document.create_element("div") {
        Ok(elem) => elem,
        Err(_) => return,
    };
    
    // 设置元素内容
    let _ = div.set_text_content(Some("Hello from Rust WASM!"));
    
    // 添加样式
    let _ = div.set_attribute("style", "color: blue; font-size: 20px; margin: 20px;");
    
    // 添加到body
    if let Some(body) = document.body() {
        let _ = body.append_child(&div);
    }
}
// 暴露给JavaScript的函数
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

//测试函数
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

更新Cargo.toml,添加web-sys依赖(在[dependencies]部分):

web-sys = { version = "0.3", features = ["Document", "Window", "Element", "HtmlElement"] }

重新编译:

wasm-pack build --target web

更新wasmtest.html,添加一个按钮来调用新函数:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Rust WASM Demo</title>
</head>
<body>
    <h1>Rust WASM 加法演示</h1>
    <input type="number" id="num1" value="2"> +
    <input type="number" id="num2" value="3"> =
    <span id="result"></span>
    <button id="calculateBtn">计算</button>
    <button id="btn_addHelloElement">添加Rust消息</button>

    <script type="module">
        // 导入WASM模块
        import init, { add,add_hello_element  } from './pkg/rust_wasm_demo.js';
        
        // 初始化WASM
        async function run() {
            await init();
            calculate(); // 初始计算
            
            // 绑定按钮点击事件
            document.getElementById('calculateBtn').addEventListener('click', calculate);
            document.getElementById('btn_addHelloElement').addEventListener('click', addHelloElement);
        }
        
        // 计算函数
        function calculate() {
            const num1 = parseInt(document.getElementById('num1').value);
            const num2 = parseInt(document.getElementById('num2').value);
            const result = add(num1, num2); // 调用Rust函数
            document.getElementById('result').textContent = result;
        }

        function addHelloElement() {
            add_hello_element(); // 调用新的Rust函数
        }        
        
        // 启动应用
        run();
    </script>
</body>
</html>
    

现在,我们重新进行测试,代开http协议下的wasmtest.html。点击【添加Rust消息】按钮三次,可以看到页面动态增加了三个div元素,其效果如下:

<div style="color: blue; font-size: 20px; margin: 20px;">Hello from Rust WASM!</div>
<div style="color: blue; font-size: 20px; margin: 20px;">Hello from Rust WASM!</div>
<div style="color: blue; font-size: 20px; margin: 20px;">Hello from Rust WASM!</div>

性能优势展示

WASM 的主要优势之一是性能。让我们创建一个计算密集型任务来展示这一点。在src/lib.rs中添加:

斐波那契数列计算函数

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

升级后的lib.rs:

mod utils;

use wasm_bindgen::prelude::*;

use web_sys::{Document, Window};

// 斐波那契数列
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

// 获取浏览器窗口对象
#[wasm_bindgen]
pub fn get_window() -> Option<Window> {
    web_sys::window()
}

// 获取文档对象
#[wasm_bindgen]
pub fn get_document() -> Option<Document> {
    web_sys::window()?.document()
}

// 创建并添加元素到页面
#[wasm_bindgen]
pub fn add_hello_element() {
    // 安全地获取文档对象
    let document = match get_document() {
        Some(doc) => doc,
        None => return,
    };
    
    // 创建一个新的div元素
    let div = match document.create_element("div") {
        Ok(elem) => elem,
        Err(_) => return,
    };
    
    // 设置元素内容
    let _ = div.set_text_content(Some("Hello from Rust WASM!"));
    
    // 添加样式
    let _ = div.set_attribute("style", "color: blue; font-size: 20px; margin: 20px;");
    
    // 添加到body
    if let Some(body) = document.body() {
        let _ = body.append_child(&div);
    }
}
// 暴露给JavaScript的函数
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

//测试函数
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

在在wasmtest.html中添加:

<h3>斐波那契计算</h3>
<input type="number" id="fibNum" value="30">
<button onclick="calculateFib()">计算斐波那契数</button>
<p id="fibResult"></p>

<script>
    // 添加到现有脚本中
    function calculateFib() {
        const num = parseInt(document.getElementById('fibNum').value);
        
        // 测试JavaScript性能
        const jsStart = performance.now();
        const jsResult = jsFibonacci(num);
        const jsTime = performance.now() - jsStart;
        
        // 测试WASM性能
        const wasmStart = performance.now();
        const wasmResult = fibonacci(num);
        const wasmTime = performance.now() - wasmStart;
        
        document.getElementById('fibResult').textContent = 
            `结果: ${wasmResult} | JS耗时: ${jsTime.toFixed(2)}ms | WASM耗时: ${wasmTime.toFixed(2)}ms`;
    }
    
    // JavaScript版本的斐波那契函数
    function jsFibonacci(n) {
        if (n === 0) return 0;
        if (n === 1) return 1;
        return jsFibonacci(n - 1) + jsFibonacci(n - 2);
    }
</script>

升级后的wasmtest.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Rust WASM Demo</title>
</head>
<body>
    <h1>Rust WASM 加法演示</h1>
    <input type="number" id="num1" value="2"> +
    <input type="number" id="num2" value="3"> =
    <span id="result"></span>
    <button id="calculateBtn">计算</button>
    <button id="btn_addHelloElement">添加Rust消息</button>
    </p>
    <input type="number" id="fibNum" value="30">
    <button id="btn_calculateFib">计算斐波那契数</button>
    <p id="fibResult"></p>

    <script type="module">
        // 导入WASM模块
        import init, { add,add_hello_element,fibonacci  } from './pkg/rust_wasm_demo.js';
        
        // 初始化WASM
        async function run() {
            await init();
            calculate(); // 初始计算
            
            // 绑定按钮点击事件
            document.getElementById('calculateBtn').addEventListener('click', calculate);
            document.getElementById('btn_addHelloElement').addEventListener('click', addHelloElement);
            document.getElementById('btn_calculateFib').addEventListener('click', calculateFib);
        }
        
        // 计算函数
        function calculate() {
            const num1 = parseInt(document.getElementById('num1').value);
            const num2 = parseInt(document.getElementById('num2').value);
            const result = add(num1, num2); // 调用Rust函数
            document.getElementById('result').textContent = result;
        }

        function addHelloElement() {
            add_hello_element(); // 调用新的Rust函数
        }        
        
        function calculateFib() {
            const num = parseInt(document.getElementById('fibNum').value);
            
            // 测试JavaScript性能
            const jsStart = performance.now();
            const jsResult = jsFibonacci(num);
            const jsTime = performance.now() - jsStart;
            
            // 测试WASM性能
            const wasmStart = performance.now();
            const wasmResult = fibonacci(num);
            const wasmTime = performance.now() - wasmStart;
            
            document.getElementById('fibResult').textContent = 
                `结果: ${wasmResult} | JS耗时: ${jsTime.toFixed(2)}ms | WASM耗时: ${wasmTime.toFixed(2)}ms`;
        }

        // JavaScript版本的斐波那契函数
        function jsFibonacci(n) {
            if (n === 0) return 0;
            if (n === 1) return 1;
            return jsFibonacci(n - 1) + jsFibonacci(n - 2);
        }
        // 启动应用
        run();
    </script>
</body>
</html>
    

重新编译并测试,浏览器显示的测试结果如下(以下是以30为参数的斐波那契函数计算结果,您的电脑性能可能超过我,所以展示出来的结果可能与我不同,但数据趋势应该是一致的,既:WASM的CPU密集运算型任务的执行速度远快于JavaScript)。

结果: 832040 | JS耗时: 32.70ms | WASM耗时: 3.00ms

笔者建议您下一步学习

恭喜你完成了 Rust WASM 的入门教程!以下是进一步学习的方向:

  1. 学习更多 wasm-bindgen、web-sys等库的更多特性:探索更复杂的类型转换和 JavaScript 交互
  2. 性能优化:学习如何使用wasm-opt等工具进一步优化 WASM 输出

如果有开发上的需要,可以:

  1. 学习 WebAssembly 的工作原理:了解 WASM 的二进制格式和执行模型
  2. 探索成熟框架:如 Yew 或 Seed,它们提供了类似 React 的组件模型