从 WIT 到 Wasmtime:构建 Rust Component 工程

目标:用一份根目录 WIT,构建 Rust Guest,再用 Rust + Wasmtime Host 强类型实例化并调用它。本文使用 cargo xtask 执行示例中的构建和验证命令。

第一篇讲清了概念;这一篇只做实战。我们刻意从一个 greet 开始,因为真正困难的地方不是业务逻辑,而是把 WIT → 生成绑定 → Component → Host 调用 这条工程链路搭正确。

将本篇放入第一篇的本地文本工具场景时,greet 可以理解为任意一次最小插件调用。后续把它改为 format(text)validate(content),不会改变本篇的工程结构。

前置知识:本文假定你已理解 WIT 的 packageinterfaceworldimportexport。如果这些概念尚不熟悉,请先阅读第一篇的 WIT 部分,并结合官方 WIT Reference 查阅完整语法。本文不会逐项展开 WIT 语法,而是聚焦 Rust 与 Wasmtime 的工程实现。

完成后的项目结构

text 复制代码
wasm-component-lab/
├── wit/world.wit          # 唯一协议源
├── src/main.rs            # Rust Host:Wasmtime
├── guests/rust/           # Rust Guest:wit-bindgen
├── guests/typescript/     # 第三篇使用
├── xtask/                 # 示例中的任务命令入口
└── docs/

一个关键选择是:**WIT 不放在某一个 Guest 目录中。**协议属于项目公共边界,而不属于 Rust 或 TypeScript 的任一实现;否则新增语言实现时容易复制 WIT 并产生不受控制的差异。

0. 准备环境

powershell 复制代码
rustup target add wasm32-wasip2
cargo xtask --help

wasm32-wasip2 是 Rust 面向 Component Model/WASI Preview 2 的目标。项目将 wasmtimewasmtime-wasi 保持相同主版本,避免嵌入 API 不匹配。

1. 从接口定义开始:wit/world.wit

wit 复制代码
package wasm-component-lab:greeter@0.1.0;

interface api {
  /// Formats a greeting for a caller-provided name.
  greet: func(name: string) -> string;
}

world plugin {
  export api;
}

这里有三层含义:

  • package 为公开契约提供稳定身份;
  • api 集中业务函数和类型;
  • plugin 是完整组件边界。export api 表示 Guest 必须提供它。

不要先在 Rust 写 trait 再"翻译"一份 WIT。WIT 必须是源头,语言绑定是生成物。

2. 建立 Rust Guest

guests/rust/Cargo.toml 的关键点是:

toml 复制代码
[lib]
crate-type = ["cdylib"]

[dependencies]
wit-bindgen = "0.61.1"

guests/rust/src/lib.rs 使用 wit-bindgen

rust 复制代码
mod bindings {
    use super::Greeter;

    wit_bindgen::generate!({
        world: "plugin",
        path: "../../wit",
    });

    export!(Greeter);
}

struct Greeter;

impl bindings::exports::wasm_component_lab::greeter::api::Guest for Greeter {
    fn greet(name: String) -> String {
        println!("rust guest: greeting {name}");
        format!("Hello, {name}! (from a Rust Wasm Component)")
    }
}

宏读取 world 后生成 Guest trait;编译器会要求 Greeter 实现它。export!(Greeter) 则把 Rust 实现暴露为 world 中定义的 export。

注意两件事:

  • 生成路径中的 wasm_component_lab 是 WIT package 名的 Rust 命名映射;名称不确定时,应以编译器诊断和生成结果为准。
  • String 只是 Rust 侧的自然类型。字符串的内存表示、释放和跨边界传递由绑定及 Canonical ABI 处理。

3. 构建 Component 产物

powershell 复制代码
cargo xtask build-rust

本示例中,build-rust 执行以下 Cargo 命令:

powershell 复制代码
cargo build -p wasm-component-lab-rust-guest --target wasm32-wasip2

产物为:target/wasm32-wasip2/debug/wasm_component_lab_rust_guest.wasm。扩展名虽然仍是 .wasm,但它是能被 Component Host 实例化的 Component artifact。

4. 建立 Host:从 WIT 生成调用侧绑定

根目录 src/main.rs 同样读取 wit/,但不实现 Guest trait,而是生成 Host 的实例化器和调用包装:

rust 复制代码
wasmtime::component::bindgen!({
    world: "plugin",
    path: "wit",
});

运行时路径如下:

rust 复制代码
let engine = Engine::default();
let component = Component::from_file(&engine, &component_path)?;

let mut linker = Linker::<HostState>::new(&engine);
wasmtime_wasi::p2::add_to_linker_sync(&mut linker)?;

let mut store = Store::new(&engine, HostState { wasi, table });
let plugin = Plugin::instantiate(&mut store, &component, &linker)?;
let greeting = plugin
    .wasm_component_lab_greeter_api()
    .call_greet(&mut store, "WIT learner")?;
sequenceDiagram participant X as xtask participant G as Rust Guest participant H as Wasmtime Host participant W as WIT bindings X->>G: wasm32-wasip2 build X->>H: cargo run + component path H->>W: bindgen! 生成调用包装 H->>G: instantiate H->>G: api.greet(&#34;WIT learner&#34;) G-->>H: WIT string

Engine 管理编译和执行配置;Component 是已加载 artifact;Linker 提供组件 imports;Store<HostState> 则是一次实例的可变状态容器。

5. 在 HostState 中管理 WASI 上下文

rust 复制代码
struct HostState {
    wasi: WasiCtx,
    table: ResourceTable,
}

本示例调用 inherit_stdio(),只是允许 Guest 使用标准输入输出。add_to_linker_sync 的作用是把 WASI 接口实现挂到 Linker;WasiCtx 决定本次运行真正授予哪些能力。两者缺一不可。

对于命令行或桌面应用,命令参数、用户选择的文件、应用配置和日志设施也应归 HostState 或其共享服务管理。Guest 可通过 WIT 导入范围明确的能力,例如 configuration.get,而不是直接持有文件句柄或完整应用状态。

6. 端到端验证

powershell 复制代码
cargo xtask smoke-rust

预期输出:

text 复制代码
rust guest: greeting WIT learner
host: Hello, WIT learner! (from a Rust Wasm Component)

smoke-rust 验证的范围不止编译成功:它覆盖 Guest 产物、Host 的 WIT 绑定、WASI Linker 注册、组件实例化与实际跨 ABI 调用。

7. 接口变更练习:使用类型系统定位适配点

把 WIT 改成:

wit 复制代码
greet: func(name: string, locale: string) -> result<string, string>;

然后运行:

powershell 复制代码
cargo xtask check

你会看到 Rust Guest、Host 调用和第三篇的 TS Guest 都需要响应。不要绕开错误去手改生成代码:这是 WIT 作为唯一协议源正在发挥作用。修好后,再决定是否应该升级 package/interface 版本。

常见报错速查

现象 先检查
can't find crate for std 是否执行了 rustup target add wasm32-wasip2
Host 加载失败 Guest 是否先构建、传入的是否是 Component artifact
unknown import / 实例化失败 world 的 imports 是否都注册到了 Linker
trait 路径不对 检查编译器诊断与 WIT 名称映射
Guest 有日志但 Host 没输出 是否注册 WASI 并在 WasiCtx 授予 stdio

下一篇:TypeScript Guest 的类型约束与工程化

参考资料

相关推荐
Kapaseker5 小时前
Rust 为什么越来越受欢迎?开发者角度谈
rust
孙启超5 小时前
【AI开发之Rust】第 6 课:结构体、枚举与方法 —— 开始定义自己的类型
开发语言·人工智能·后端·ai·rust
foolishlee5 小时前
Rust 下载依赖包
rust
蓝宝石的傻话5 小时前
MiBee Eye 蜂眼:把闲置的 Linux 小板做成一台能进国标平台的摄像头
golang·rust
传奇开心果编程6 小时前
【Rust入门知识点学与练】第36课:所有权——移动、借用、Copy、切片
开发语言·学习·rust
传奇开心果编程6 小时前
【Rust入门知识点学与练】第35课:工具链与反馈环——先学会读编译器在说什么
开发语言·学习·rust
刘发财14 小时前
前端2秒生成500页矢量PDF,rust真的强到没朋友
前端·javascript·rust
yume_sibai16 小时前
06-Rust Web 开发实战(Axum 框架 + 数据库 + JWT 认证 + 中间件 + 部署)
前端·数据库·rust
PC2005-cloud21 小时前
Rust学习笔记:控制流——if、loop、while、for与match
笔记·学习·rust
IT笔记1 天前
Rust 迭代器多级链式调用执行顺序笔记
开发语言·笔记·rust