LoginSignup
4
1

cargo componentでWASI Preview 2を使ったコンポーネントを作成

Posted at

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 をローカル参照する事で対応してみました。

WASI の取得
$ git clone https://github.com/WebAssembly/WASI

とりあえず、次のように Cargo.toml の package.metadata.component.target.dependencies へ WASI Preview 2 のパッケージ毎の配置先ディレクトリを設定する事で依存関係を解決しました。(もっとスマートな方法があるかもしれませんが)

Cargo.toml(wasi:cli の依存設定)
[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 しています。

wit/world.wit
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 の実装は次のようになりました。

src/lib.rs
#[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 }
  1. wasm32-wasi は println! のような Rust のコードを WASI Preview 1 の API 呼び出しへ置き換えてビルドするためのもので、今のところ Preview 2 には対応していない。cargo component はこのビルドを Preview 2 へ適応させる事が可能。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1