cargo component で WASI Preview 2 を使ったコンポーネントを作成してみました。
はじめに
WASI は WebAssembly から OS の機能を呼び出すための API です。
Preview 2 ではコンポーネント用に wit で API が定義されており、Preview 1 よりも色々と扱い易くなっているように思います。
今回は、この Preview 2 の API を import したコンポーネントを作成して wasmtime で動作確認します。
なお、cargo component には Preview 2 未対応の wasm32-wasi 1 ビルドを Preview 2 へ自動適応させる機能があるようですが、この機能は使わずに直接 Preview 2 の API を呼び出します。
コンポーネントの作成
まずは、プロジェクトを作成します。
$ cargo component new --lib sample
$ cd sample
cargo component は 前回 試したバージョンから次のような点が変更されています。
-
--reactor
が--lib
に置き換わった(--reactor も一応使える) - bindings.rs を src ディレクトリへ生成するようになった
- Cargo.toml の dependencies の構成が変わった
wit 依存関係の解決
WASI Preview 2 を import する場合、次のような課題が生じました。
- wit の依存関係をどう解決するか
cargo component では、warg というプロトコルに対応したレジストリを使用する想定のようですが、現時点で公式レジストリのようなものは公開されていないようで、WebAssembly Registry (Warg) をローカルで立ち上げる必要がありそうです。
今回、レジストリは使わずに、github の WASI から取得した wit をローカル参照する事で対応してみました。
$ git clone https://github.com/WebAssembly/WASI
とりあえず、次のように Cargo.toml の package.metadata.component.target.dependencies
へ WASI Preview 2 のパッケージ毎の配置先ディレクトリを設定する事で依存関係を解決しました。(もっとスマートな方法があるかもしれませんが)
[package]
name = "sample"
version = "0.1.0"
edition = "2021"
[dependencies]
bitflags = "2.4.2"
wit-bindgen-rt = "0.22.0"
[lib]
crate-type = ["cdylib"]
[package.metadata.component]
package = "component:sample"
[package.metadata.component.target.dependencies]
"wasi:cli" = { path = "./WASI/preview2/cli" }
"wasi:clocks" = { path = "./WASI/preview2/clocks" }
"wasi:filesystem" = { path = "./WASI/preview2/filesystem" }
"wasi:io" = { path = "./WASI/preview2/io" }
"wasi:random" = { path = "./WASI/preview2/random" }
"wasi:sockets" = { path = "./WASI/preview2/sockets" }
ちなみに、これは wasi:cli
を使うための設定です。
wasi:cli の world が clocks 等のパッケージを import しており、それらの設定も必要でした。
実装
WASI Preview 1 では _start
関数がコマンド実行時のエントリポイントでしたが、Preview 2 では run
に変更されています。
wasi:cli/run
を export する事で Preview 2 のコマンド実行に対応できるようです。
また、コンポーネント内で stdout
による標準出力と wall-clock
による日付の取得を行うため、それぞれを import しています。
package component:sample;
world sample {
import wasi:cli/stdout@0.2.0;
import wasi:clocks/wall-clock@0.2.0;
export wasi:cli/run@0.2.0;
}
この wit の実装は次のようになりました。
#[allow(warnings)]
mod bindings;
use bindings::exports::wasi::cli::run::Guest;
use bindings::wasi::cli::stdout::get_stdout;
use bindings::wasi::clocks::wall_clock;
struct Component;
impl Guest for Component {
// run 関数の実装(コマンドライン実行時のエンドポイント)
fn run() -> Result<(), ()> {
log("called run").map_err(|_| ())?;
Ok(())
}
}
fn log(msg: &str) -> Result<(), Box<dyn std::error::Error>> {
let s = format!("msg={}, {:?}", msg, wall_clock::now());
let stdout = get_stdout();
// 標準出力への出力
stdout.write(s.as_bytes())?;
stdout.flush()?;
Ok(())
}
bindings::export!(Component with_types_in bindings);
ビルド
import した Preview 2 の API を呼び出しているだけなので wasm32-unknown-unknown
でビルドします。
$ cargo component build --target wasm32-unknown-unknown --release
動作確認
wasmtime は WASI Preview 2 に対応しているので、wasmtime run
で普通に実行できました。
$ wasmtime run ./target/wasm32-unknown-unknown/release/sample.wasm
msg=called run, Datetime { seconds: 1710661600, nanoseconds: 83798000 }
-
wasm32-wasi は println! のような Rust のコードを WASI Preview 1 の API 呼び出しへ置き換えてビルドするためのもので、今のところ Preview 2 には対応していない。cargo component はこのビルドを Preview 2 へ適応させる事が可能。 ↩