WippyでRustを実行する
Rust WebAssemblyコンポーネントをビルドし、関数、CLIコマンド、HTTPエンドポイントとして実行します。
分類: 外部Rustコンポーネントツールチェーンを使用する実行可能なチュートリアルです。 WIT、Rust実装、Wippyレジストリ、整合性ハッシュの手順、コマンド、期待結果、失敗時の確認方法を掲載しています。
構築するもの
4つのエクスポート関数を持つRustコンポーネント:
- greet - 名前を受け取り、挨拶を返す
- add - 2つの整数を加算する
- fibonacci - n番目のフィボナッチ数を計算する
- list-files - マウントされたディレクトリ内のファイルを一覧表示する
これらを呼び出し可能な関数、CLIコマンド、HTTPエンドポイントとして公開します。
前提条件
- Wippyランタイム
v0.3.32a。 wasm32-wasip1ターゲットを持つRustツールチェーン。- 動作するCツールチェーン。Linuxでは
cargo-componentにOpenSSL開発ライブラリも必要です。 - このチュートリアルで使用するcargo-component 0.21.1。
rustup target add wasm32-wasip1
cargo install cargo-component --version 0.21.1 --locked
生成済みコンポーネントのひな形とWippyディレクトリを作成します:
mkdir rust-wasm-demo
cd rust-wasm-demo
cargo component new --lib demo
mkdir -p app/src/demo/wasm
PowerShellの場合:
New-Item -ItemType Directory -Path rust-wasm-demo
Set-Location rust-wasm-demo
cargo component new --lib demo
New-Item -ItemType Directory -Path app\src\demo\wasm -Force
cargo component newは互換性のあるCargo.toml、src/lib.rs、WITファイルを作成し、
後からsrc/bindings.rsも再生成します。生成されたwit-bindgen-rtのバージョンは、インストールした
cargo-componentと組み合わせたままにしてください。このインターフェースは実験的であり、
生成コードの互換性はバージョン間で保証されません。
プロジェクト構造
rust-wasm-demo/
├── demo/ # Rust component
│ ├── Cargo.toml
│ ├── wit/
│ │ └── world.wit # WIT interface
│ └── src/
│ ├── bindings.rs # generated by cargo-component
│ └── lib.rs # implementation
└── app/ # Wippy application
├── wippy.lock
└── src/
├── _index.yaml # Infrastructure
└── demo/
├── _index.yaml # CLI processes
└── wasm/
├── _index.yaml # WASM entries
└── demo_component.wasm # Compiled binary
ステップ1: WITインターフェースの作成
WIT(WebAssembly Interface Types)はホストとゲスト間のコントラクトを定義します:
demo/wit/world.witを作成します:
package component:demo;
world demo {
export greet: func(name: string) -> string;
export add: func(a: s32, b: s32) -> s32;
export fibonacci: func(n: u32) -> u64;
export list-files: func(path: string) -> string;
}
各エクスポートはWippyが呼び出せる関数になります。
ステップ2: Rustで実装する
生成されたdemo/Cargo.tomlをそのまま使用します。パッケージメタデータはWITパッケージと一致する
component:demoを対象とし、ライブラリのcrate typeはcdylibのままにしてください。
demo/src/lib.rsを作成します:
#[allow(warnings)]
mod bindings;
use bindings::Guest;
struct Component;
impl Guest for Component {
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn fibonacci(n: u32) -> u64 {
if n <= 1 {
return n as u64;
}
let (mut a, mut b) = (0u64, 1u64);
for _ in 2..=n {
let next = a + b;
a = b;
b = next;
}
b
}
fn list_files(path: String) -> String {
let mut result = String::new();
match std::fs::read_dir(&path) {
Ok(entries) => {
for entry in entries {
match entry {
Ok(e) => {
let name = e.file_name().to_string_lossy().to_string();
let meta = e.metadata();
let (kind, size) = match meta {
Ok(m) => {
let kind = if m.is_dir() { "dir" } else { "file" };
(kind, m.len())
}
Err(_) => ("?", 0),
};
let line = format!("{:<6} {:>8} {}", kind, size, name);
println!("{}", line);
result.push_str(&line);
result.push('\n');
}
Err(e) => {
let line = format!("error: {}", e);
eprintln!("{}", line);
result.push_str(&line);
result.push('\n');
}
}
}
}
Err(e) => {
let line = format!("cannot read {}: {}", path, e);
eprintln!("{}", line);
result.push_str(&line);
result.push('\n');
}
}
result
}
}
bindings::export!(Component with_types_in bindings);
bindingsモジュールはWIT定義からcargo-componentによって生成されます。
ステップ3: コンポーネントのビルド
cd demo
cargo component build --release
これによりtarget/wasm32-wasip1/release/demo.wasmが生成されます。Wippyアプリにコピーします:
mkdir -p ../app/src/demo/wasm
cp target/wasm32-wasip1/release/demo.wasm ../app/src/demo/wasm/demo_component.wasm
PowerShellの場合:
New-Item -ItemType Directory -Path ..\app\src\demo\wasm -Force
Copy-Item -LiteralPath target\wasm32-wasip1\release\demo.wasm `
-Destination ..\app\src\demo\wasm\demo_component.wasm
整合性検証用のSHA-256ハッシュを取得します:
sha256sum ../app/src/demo/wasm/demo_component.wasm
PowerShellの場合:
(Get-FileHash ..\app\src\demo\wasm\demo_component.wasm -Algorithm SHA256).Hash.ToLowerInvariant()
64文字の小文字16進数を、後述するすべてのYOUR_HASH_HEREへコピーします。最終フィールドは
sha256:<64-hex-characters>形式にします。Rustソースや元のビルドパスではなく、コピーしたバイナリのハッシュです。
ステップ4: Wippyアプリケーション
インフラストラクチャ
app/src/_index.yamlを作成します:
version: "1.0"
namespace: demo
entries:
- name: gateway
kind: http.service
meta:
comment: HTTP server
addr: ":8090"
lifecycle:
auto_start: true
- name: api
kind: http.router
meta:
comment: Public API router
server: demo:gateway
prefix: /
- name: processes
kind: process.host
lifecycle:
auto_start: true
- name: terminal
kind: terminal.host
lifecycle:
auto_start: true
- name: policy
kind: security.policy
meta:
comment: Grants access to mounted filesystems and WASM functions
policy:
actions:
- fs.get
- funcs.call
resources: "*"
effect: allow
WASMモジュールへのファイルシステムのマウントと、WASM関数の呼び出しはどちらも保護されたアクションです。ポリシーがそれらを許可し、必要とするエントリがそれを参照します。
WASM関数
app/src/demo/wasm/_index.yamlを作成します:
version: "1.0"
namespace: demo.wasm
entries:
- name: assets
kind: fs.directory
meta:
comment: Filesystem with WASM binaries
directory: ./src/demo/wasm
- name: greet_function
kind: function.wasm
meta:
comment: Greet function via payload transport
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: greet
pool:
type: inline
- name: add_function
kind: function.wasm
meta:
comment: Add function via payload transport
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: add
pool:
type: inline
- name: fibonacci_function
kind: function.wasm
meta:
comment: Fibonacci function via payload transport
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: fibonacci
pool:
type: inline
ポイント:
- 単一の
fs.directoryエントリがWASMバイナリを提供する - 複数の関数が同じバイナリを異なる
method値で参照する hashフィールドがロード時にバイナリの整合性を検証するinlineプールは1つのウォームインスタンスを介して呼び出しを直列化し、同期呼び出しごとに実行状態をリセットする。 並行ワーカーが必要な場合は別のプール種別を使用する
WASIを使用した関数
list-files関数はファイルシステムにアクセスするため、WASIインポートが必要です:
- name: list_files_function
kind: function.wasm
meta:
comment: Filesystem listing with WASI mounts
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: list-files
imports:
- wasi:cli
- wasi:io
- wasi:clocks
- wasi:filesystem
wasi:
mounts:
- fs: demo.wasm:assets
guest: /data
pool:
type: inline
wasi.mountsセクションはWippyファイルシステムエントリをゲストパスにマッピングします。WASMモジュール内では、/dataがdemo.wasm:assetsディレクトリを指します。
CLIコマンド
app/src/demo/_index.yamlを作成します:
version: "1.0"
namespace: demo.cli
entries:
- name: wasm_cli_policy
kind: security.policy
policy:
actions:
- fs.get
resources:
- demo.wasm:assets
effect: allow
- name: ls
kind: process.wasm
meta:
comment: List files from mounted WASI filesystem
command:
name: ls
short: List files from mounted directory
security:
actor: {id: demo.cli:ls}
policies: [demo:policy]
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: list-files
imports:
- wasi:cli
- wasi:io
- wasi:clocks
- wasi:filesystem
wasi:
mounts:
- fs: demo.wasm:assets
guest: /data
meta.commandブロックはプロセスを名前付きCLIコマンドとして登録します。greetコマンドは文字列操作のみを使用するためWASIインポートは不要です。lsコマンドはファイルシステムアクセスが必要なため、マウントを許可するセキュリティコンテキストも携えます。
HTTPエンドポイント
app/src/demo/wasm/_index.yamlに追加します:
- name: http_greet
kind: function.wasm
meta:
comment: Greet exposed via wasi-http transport
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: greet
transport: wasi-http
pool:
type: inline
- name: http_greet_endpoint
kind: http.endpoint
meta:
comment: HTTP POST endpoint for WASM greet
router: demo:api
method: POST
path: /greet
func: http_greet
wasi-httpトランスポートはHTTPリクエスト/レスポンスコンテキストをWASMの引数と結果にマッピングします。
ステップ5: 初期化と実行
cd app
wippy init
CLIコマンドの実行
# List available commands
wippy run list
Available commands:
greet Greet someone via WASM (demo.cli:greet)
ls List files from mounted directory (demo.cli:ls)
Run with: wippy run <command>
コマンド名の後の引数は、エクスポートされた関数に文字列パラメータとして渡されます。そのため各コマンドは、そのWITシグネチャが宣言する引数をちょうど受け取ります:
# Run greet
wippy run greet World
Hello, World!
# Run ls to list mounted directory
wippy run ls /data
コマンドは少なくともdemo_component.wasmとそのファイルサイズを表示し、ステータス0で終了します。
Wippyは任意のprocess.wasm戻り値を表示しないため、CLI例ではWASI stdoutへ書き込むRust関数を使用しています。
サービスとして実行
wippy run
HTTPサーバーがポート8090で起動します。wasi-httpトランスポートは、リクエストボディを関数の単一の文字列引数として渡します:
curl -X POST http://localhost:8090/greet -d 'World'
Hello, World!
Luaからの呼び出し
WASM関数はLua関数と同じ方法で呼び出されます。呼び出し元のプロセスには対象へのfuncs.callが必要で、これはdemo:policyが許可します:
local funcs = require("funcs")
local greeting, err = funcs.call("demo.wasm:greet_function", "World")
-- greeting: "Hello, World!"
local sum, err = funcs.call("demo.wasm:add_function", 6, 7)
-- sum: 13
local fib, err = funcs.call("demo.wasm:fibonacci_function", 10)
-- fib: 55
トラブルシューティングとクリーンアップ
cargo componentが見つからない場合はインストールしてcargo component buildを再実行してください。 通常のcargo buildでは、この構成と同じbindingsやコンポーネント出力は生成されません。- 最初のビルド前に
src/bindings.rsがないのは正常です。cargo component build後もなければ、 WITパッケージまたはコンポーネントメタデータを解決できていません。バイナリをコピーする前にビルドエラーを修正してください。 WASM hash mismatchは、ハッシュ計算後にバイナリが変わったか、プレースホルダーが残っていることを示します。 リリースバイナリを再コピーし、ダイジェストを再計算して、参照するすべてのエントリを更新してください。- importのインスタンス化エラーは、コンポーネントが使用するホストプロファイルをエントリで省略したことを示します。
ファイルシステム例では記載の
wasi:cli、wasi:io、wasi:clocks、wasi:filesystemimportを維持してください。 cannot read /dataは、wasi.mountsのguest pathまたはファイルシステムエントリがレジストリと一致しないことを示します。- Ctrl+CでHTTPランタイムを停止します。Rustビルド出力は
demo/target/に残ります。 生成物を消すには、そのディレクトリとコピーした.wasmファイルを削除してください。