Hello, world! with Wasm Component(依存するインターフェース編)
これまでのあらすじ:
-
第1回
- cargo-componentを使ってWebAssemblyコンポーネント(Wasmコンポーネント)を作りました
- Wasmtimeで実行しました
- wasm-toolsを使って作成したコンポーネントのワールドを出力しました
-
第2回
- cargo-componentを使ってライブラリーとして働くWasmコンポーネントを作成しました
- WebAssembly Interface Typeでライブラリーのインターフェースを定義し、Rustでインターフェースを実装しました
- Wasmコンポーネントレジストリーであるwa.devに、作成したコンポーネントを登録しました
-
第3回
- wit toolを使ってWITパッケージをレポジトリーに登録しました
- レポジトリーに公開されているWITパッケージをRustで実装しました
今回は前回に引き続きWITパッケージと、その実装について扱います。今回は第3回に作成したusername:name
パッケージに依存する別のWITパッケージを定義し、そのパッケージの実装をRustで行います。
username:name
に依存するインターフェース
あるWITパッケージに依存する別のWITパッケージも定義できます。依存する、とは次のような場合を指します:
- WITパッケージに定義されているインターフェースを利用したワールドの定義を行う場合
- WITパッケージに定義されているデータ型を利用して、インターフェースを定義する場合
username:name
パッケージにはname-provider
インターフェースが定義されています。このインターフェースには関数しか定義されていないので、必然的に今回作成するパッケージは前者のパターン、つまりname-provider
インターフェースをインポートするワールドを定義します。
準備
第3回までで、プロジェクトフォルダーは次のようになっています。
hello-wasi-cli
├── Cargo.lock
├── Cargo.toml
├── crates
│ ├── greet
│ │ ├── Cargo.toml
│ │ └── src
│ └── name-impl
│ ├── Cargo.toml
│ └── src
├── interfaces
│ └── name
│ ├── wit.lock
│ ├── wit.toml
│ └── world.wit
├── src
│ ├── bindings.rs
│ └── main.rs
└── target
ここにinterfaces/hello-wasi
フォルダーを追加し、wit init
コマンドでWITパッケージようフォルダーとして初期化します:
% pwd
/somewhere/hello-wasi-cli
% mkdir -p interfaces/hello-wasi
% wit init interfaces/hello-wasi
Created configuration file `interfaces/hello-wasi/wit.toml`
username:name
に依存するWITパッケージ
username:name
で定義されているインターフェースname-provider
は、次のようにパスで表現できます:username:name/name-provider
。
これを踏まえて、次のようなワールドを定義します。このワールドは、username:name
で定義されるインターフェースname-provider
をインポートする、としています:
package username:hello-wasi;
world default {
import username:name/name-provider;
}
これをWITパッケージとして公開しようとすると、次のようなエラーが発生します。これはそのWITパッケージのマニフェストに、username:name
が依存するパッケージであると記述されていないためです:
error: failed to merge package from directory `/somewhere/hello-wasi-cli/interfaces/hello-wasi`
Caused by:
package not found
--> /somewhere/hello-wasi-cli/interfaces/hello-wasi/world.wit:4:12
|
4 | import username:name/name-provider;
| ^------------
依存するWITパッケージの追加
依存するWITパッケージは、wit add
コマンドで追加できます:
% pwd
/somewhere/hello-wasi-cli/interfaces/hello-wasi
% wit add username:name
Updating component registry package logs
Added dependency `username:name` with version `0.1.0`
コマンド実行後、マニフェストであるwit.toml
には[dependencies]
セクションが追加されています:
version = "0.1.0"
[dependencies]
"username:name" = "0.1.0"
WITパッケージのビルド
wit build
コマンドでWITパッケージをビルドできます。ビルドすることで、WITファイルにエラーがないことや、依存関係が正しく設定されていることを確認できます:
% wit build
Updating component registry package logs
Created package `hello-wasi.wasm`
ビルドが成功すると、パッケージ名のWasmファイルが作成されます:
% ls
hello-wasi.wasm wit.lock wit.toml world.wit
なおwit.lock
には依存関係に追加したWITファイルのバージョン、ハッシュ値などが記述されています。
レポジトリーへの公開
次の手順でwa.devにusername:hello-wasi
を公開します。
- wa.devのWeb UIでパッケージを作成
-
wit pubilsh
コマンドを実行して登録
hello-wasi-cli
に実装
公開したusername:hello-wasi
をhello-wasi-cli
に実装します。hello-wasi-cli
は、プロジェクトフォルダーのルートにあるバイナリークレートです:
hello-wasi-cli
├── Cargo.lock
├── Cargo.toml
├── crates
│ ├── greet
│ │ ├── Cargo.toml
│ │ └── src
│ └── name-impl
│ ├── Cargo.toml
│ └── src
├── interfaces
│ ├── hello-wasi
│ │ ├── hello-wasi.wasm
│ │ ├── wit.lock
│ │ ├── wit.toml
│ │ └── world.wit
│ └── name
│ ├── wit.lock
│ ├── wit.toml
│ └── world.wit
├── src
│ ├── bindings.rs
│ └── main.rs
└── target
Cargo.toml
の編集
Cargo.toml
を編集して、実装するワールドを指定します:
-
[package.metadata.component]
セクションにtarget
属性を追加します - 追加した
target
属性の値にusername:hello-wasi
を設定します
以下が変更例です:
workspace = { members = ["crates/greet", "crates/name-impl"] }
[package]
name = "hello-wasi-cli"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/referen
ce/manifest.html
[dependencies]
bitflags = "2.5.0"
wit-bindgen-rt = "0.24.0"
[package.metadata.component]
package = "username:hello-wasi-cli"
target = "username:hello-wasi"
インポートする関数の利用
username:hell-wasi
はusername:name/name-provider
に定義されている関数を利用できます。
package username:hello-wasi;
world default {
import username:name/name-provider;
}
username:name/name-provider
には次のように、name()
という文字列を返す関数が定義されています。
package username:name;
interface name-provider {
name: func() -> string;
}
この関数はwit-bindgenが生成したコードによって、Rustの関数として利用できます。インポートされた関数は次のモジュールパスで表現されます:
bindings::<WITパッケージの名前空間>::<WITパッケージ名>::<インターフェース名>::<関数名>
上記のname
関数の場合、次のようになります。
bindings::username::name::name_provider::name;
ビルド
cargo component build
コマンドでwasm32-wasi
をターゲットにビルドすると、wasi:cli/run
をエキスポートしたCLIプログラムとしてビルドします。これはターゲットに指定したワールドにwasi:cli/run
をエキスポートすることが明示されていなくても、cargo component
がそのようにビルドします。
まとめ
- 他のWITパッケージに依存するWITパッケージを定義できます
- 依存するインターフェースに定義されている関数やデーター型は、ワールド定義やインターフェースの定義に利用できます
- 依存するインターフェースに定義されている関数やデーター型は、定義に基づいてコードが生成され、Rustのプログラムでも利用できます
なお生成されたhello-wasi-cli.wasm
は、依存するusername:name/name
の実装を持たないため実行できません:
% wasmtime target/wasm32-wasi/release/hello-wasi-cli.wasm
Error: failed to run main module `target/wasm32-wasi/release/hello-wasi-cli.wasm`
Caused by:
0: component imports instance `username:name/name-provider`, but a matching implementation was not found in the linker
1: instance export `name` has the wrong type
2: function implementation is missing
実行するためには、username:name/name
を実装したWasmコンポーネントと合成する必要があります。
Discussion