🧩

Hello, world! with Wasm Component

2024/04/10に公開
2

TL;DR

  • cargo-componentを使うと、Wasmコンポーネントを作成するためのRustプロジェクトを作成できます
  • 作成したWasmコンポーネントはWasmtimeを使って実行できます
  • 作成したWasmコンポーネントはwasi:cli/runインターフェースを実装しています

必要なツール

必要なツールは次の4つです。

  • Rustのツールチェーン
  • cargo-component
  • Wasmtime
  • wasm-tools

Rustのツールチェーンはこちらに説明があります

cargo-component

cargo-componentは、Wasmコンポーネントの管理を行うためのRustのパッケージ管理ツールであるCargoのサブコマンドです。今回はプロジェクトの作成とビルドに利用します。

cargo-componentはCargoを使ってインストールできます:

% cargo install cargo-component

cargo-componentではcargoコマンドと同様のサブコマンドが利用できます:

Usage: cargo component <COMMAND>

Commands:
  add      Add a dependency for a WebAssembly component
  key      Manage signing keys for publishing components to a registry
  new      Create a new WebAssembly component package at <path>
  update   Update dependencies as recorded in the component lock file
  publish  Publish a package to a registry
  help     Print this message or the help of the given subcommand(s)

Wasmtime

WasimtimeはWasmの処理系の1つです。ビルドしたWasmコンポーネントの実行に利用します。

次のようにインストーラーを利用してインストールします:

curl https://wasmtime.dev/install.sh -sSf | bash

wasmtimeは~/.wasmtime/bin/wasmtimeにインストールされます。またインストーラーは~/.wasmtime/binをコマンドサーチパスに追加するため、シェルの設定を変更する必要は特にありません。

次のようにwasmtimeコマンドを実行して、インストールできたことを確認します:

% wasmtime --version
wasmtime-cli 19.0.1 (26104f0c7 2024-04-02)

wasm-tools

wasm-toolsはWasmファイルを操作するツール群です。例えばWasmファイルから、そのテキスト表現であるWATファイルを生成できます。

今回の内容にwasm-toolsは必須ではありませんが、ビルドされたWasmコンポーネントが依存するシステムインターフェース群の確認に利用します。

wasm-toolsもCargoを使ってインストールできます:

% cargo install wasm-tools

Hello, world!

まずRustプロジェクトを作成します。次の例ではcargo-componentのサブコマンドnewを使ってhello-wasi-cliプロジェクトを作成しています:

% cargo component new hello-wasi-cli
     Created binary (application) `hello-wasi-cli` package

作成したプロジェクトは次のような構造をしています。Rustのバイナリークレートと同じフォルダー構成となっています:

.
├── Cargo.toml
└── src
   └── main.rs

main.rsは次のようになっています。これもcargo newで作成したバイナリークレートの初期状態と同じ内容になっています:

fn main() {
    println!("Hello, world!");
}

ビルド

cargo component buildコマンドを実行し、ビルドします:

% cargo component build
   Compiling hello-wasi-cli v0.1.0 (/somewhere/hello-wasi-cli)
    Finished dev [unoptimized + debuginfo] target(s) in 0.68s

ビルドされたWasmファイルはtarget/wasm32-wasi/debug/hello-wasi-cli.wasmに出力されます。devloperプロファイルでビルドを行っていること、またwasm32-wasiをターゲットにビルドを行っていることが理由です。

% ls target/wasm32-wasi/debug/hello-wasi-cli.wasm
target/wasm32-wasi/debug/hello-wasi-cli.wasm

実行

Wasmtimeを使ってビルドしたWasmファイルを実行します:

% wasmtime target/wasm32-wasi/debug/hello-wasi-cli.wasm
Hello, world!

ビルドされたのはWasmモジュール

これまでの手順でビルドされたのはWasmモジュールです。次のようにwasm-toolsコマンドを実行して、出力されたWAT形式のファイルを眺めると、先頭にmoduleの文字が見えると思います。

% wasm-tools print target/wasm32-wasi/debug/hello-wasi-cli.wasm | head
(module
  (type (;0;) (func))
  (type (;1;) (func (param i32)))
  (type (;2;) (func (param i32 i32)))
  (type (;3;) (func (param i32) (result i32)))
  (type (;4;) (func (param i32 i32) (result i32)))
  (type (;5;) (func (param i32 i32 i32)))
  (type (;6;) (func (param i32 i32 i32) (result i32)))
  (type (;7;) (func (param i32 i32 i32 i32) (result i32)))
  (type (;8;) (func (result i32)))

Hello,World!(コンポーネント編)

前節で作成したhello-wasm-cliプロジェクトを変更し、Wasmコンポーネントとしてビルドします。

プロジェクトは次のようなフォルダー構成をしています:

.
├── Cargo.toml
└── src
   └── main.rs

マニフェストへの追記

プロジェクトのルートフォルダにあるCargo.tomlにはhello-wasm-cliクレートに関するメタデータが記載されています。これを次のように変更します。

[package]
name = "hello-wasi-cli"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

[package.metadata.component]
package = "example:hello-world"

次の2行を追加した点が差分になります:

[package.metadata.component]
package = "example:hello-wasi-cli"

全てのWasmコンポーネントにはパッケージIDが付いています。上記の変更は、ビルドされるWasmコンポーネントのパッケージを指定しています。

このパッケージIDは、コンポーネントをコンポーネントレジストリーで公開する際に利用されます。

ビルド

前節と同様にcargo component buildコマンドでビルドします。ビルドした結果、target/wasm32-wasi/debug/hello-wasi-cli.wasmが出力されます。

% cargo component build
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s

実行

Wasmtimeを利用して、実行します。前節と同様の結果が得られます:

% wasmtime target/wasm32-wasi/debug/hello-wasi-cli.wasm
Hello, world!

コンポーネントがビルドされている

次のようにwasm-toolsを使ってビルドされたWasmファイルを確認します。前節と異なり、1行目にcomponentとあるのがわかると思います。これはビルドされたファイルはWasmコンポーネントであることを示してます:

% wasm-tools print target/wasm32-wasi/debug/hello-wasi-cli.wasm | head
(component
  (type (;0;)
    (instance
      (type (;0;) (tuple string string))
      (type (;1;) (list 0))
      (type (;2;) (func (result 1)))
      (export (;0;) "get-environment" (func (type 2)))
    )
  )
  (import "wasi:cli/environment@0.2.0" (instance (;0;) (type 0)))

ワールドの出力

Wasmコンポーネントはwasm-toolsで処理することで、次の情報をWebAssembly Interface Type(WIT)形式で出力できます:

  • コンポーネントが依存するインターフェース
  • コンポーネントが実装するインターフェース

なおこの2つを取りまとめた記述のことをWITでは「ワールド(world)」と呼びます。

ワールドの出力は次のように行います:

% wasm-tools component wit target/wasm32-wasi/debug/hello-wasi-cli.wasm
package root:component;

world root {
  import wasi:cli/environment@0.2.0;
  import wasi:cli/exit@0.2.0;
  import wasi:io/error@0.2.0;
  import wasi:io/streams@0.2.0;
  import wasi:cli/stdin@0.2.0;
  import wasi:cli/stdout@0.2.0;
  import wasi:cli/stderr@0.2.0;
  import wasi:clocks/wall-clock@0.2.0;
  import wasi:filesystem/types@0.2.0;
  import wasi:filesystem/preopens@0.2.0;

  export wasi:cli/run@0.2.0;
}

importは依存するインターフェース、exportは実装されているインターフェースを表します。ビルドしたWasmコンポーネントは、WASI 0.2で定義されているwasi:cli/runインターフェースを実装していることがわかります。

まとめ

  • cargo-componentを使うことで、通常のRustプロジェクト開発と同様にWasmコンポーネントを開発できます
  • コンポーネントをビルドするには、Cargo.tomlでWasmコンポーネントのパッケージIDを設定する必要があります
  • wasm-toolsを使えば、Wasmコンポーネントのワールドを知ることができます

次回

次回はライブラリークレートからWasmコンポーネントを作成し、コンポーネントレジストリーへ登録します:Hello, world! with Wasm Component(レイブラリー編)

Discussion

kanaruskanarus

( 本筋と関係ないですが )

typo
  • Hello, world! > ビルド

    % argo component build

  • Hello,World!(コンポーネント編) > コンポーネントがビルドされている

    前節とこなり