目标:用一份根目录 WIT,构建 Rust Guest,再用 Rust + Wasmtime Host 强类型实例化并调用它。本文使用
cargo xtask执行示例中的构建和验证命令。
第一篇讲清了概念;这一篇只做实战。我们刻意从一个 greet 开始,因为真正困难的地方不是业务逻辑,而是把 WIT → 生成绑定 → Component → Host 调用 这条工程链路搭正确。
将本篇放入第一篇的本地文本工具场景时,greet 可以理解为任意一次最小插件调用。后续把它改为 format(text) 或 validate(content),不会改变本篇的工程结构。
前置知识:本文假定你已理解 WIT 的
package、interface、world、import和export。如果这些概念尚不熟悉,请先阅读第一篇的 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 的目标。项目将 wasmtime 与 wasmtime-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")?;
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 的类型约束与工程化。