LoginSignup
8
2

More than 1 year has passed since last update.

wit-bindgen で WebAssembly Component Model のコンポーネントを作ってみる

Posted at

wit-bindgen で WebAssembly Component Model のコンポーネントを作って実行してみました。

WebAssembly Component Model は、WebAssembly で作ったコンポーネントを組み合わせてアプリケーションを作るための仕組みで、基本的には WIT(Wasm Interface Type)と呼ばれる IDL を使ってコンポーネントのインターフェースを定義するようになっているようです。

個人的には、以前からマイクロサービスの次は WebAssembly によるビジネスロジック等のコンポーネント化が来るだろうなと考えていたので、この技術には期待しています。

(a) コンポーネントの作成

とりあえず現時点では、次のような手順でコンポーネントを作れば良さそうです。

  1. wit-bindgen を使ってコンポーネントの元となる WebAssembly を作成
  2. 作成した WebAssembly を wasm-tools で処理してコンポーネントを作成

WIT の作成

wit-bindgen はデフォルトで ./wit ディレクトリを参照するようになっているようなので、そこへ次のようなファイルを作成しました。

import で外部からインポートする関数を、export で外部へエクスポートする関数を定義します。

なお、今回のバージョンでは関数名の一部に -_ を使うと上手くいかなかったので使わないようにしています。

sample/wit/sample.wit
default world sample {
    enum log-level {
        debug,
        info,
    }

    record item {
        id: string,
        value: u32,
    }

    import log: func(level: log-level, msg: string)
    import getitem: func(id: string) -> item

    export run: func()
}

実装

wit_bindgen::generate! マクロで WIT ファイルに基づいたコードを生成してくれるので、これを使ってコンポーネントの処理を実装します。

sample/src/lib.rs
wit_bindgen::generate!("sample");

struct Component;

impl Sample for Component {
    // エクスポートする関数の実装
    fn run() {
        // インポート関数 log の呼び出し
        log(LogLevel::Debug, "called run");
        // インポート関数 getitem の呼び出し
        let r = getitem("item-1");

        log(LogLevel::Info, &format!("item: {:?}", r));
    }
}

export_sample!(Component);

ビルドとコンポーネント作成

次のような Cargo.toml を使用しました。

sample/Cargo.toml
[package]
name = "sample"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wit-bindgen = "0.3"

[profile.release]
opt-level = "s"
lto = true

今回は WASI を使っていないので wasm32-unknown-unknown でビルドします。

ビルド例
$ cargo build --release --target wasm32-unknown-unknown

ビルド結果の WebAssembly を wasm-tools component new で処理してコンポーネントを作成します。

コンポーネント作成例
$ wasm-tools component new target/wasm32-unknown-unknown/release/sample.wasm -o sample-component.wasm

作成された sample-component.wasm がコンポーネントとなります。

(b) コンポーネントの実行

作成したコンポーネントを実行してみます。

現時点では WebAssembly Component Model に対応したランタイムを使う必要があるようなので、今回は wasmtime を使いました。

wasmtime v6.0 では実行できなかったので github から取得(現時点では v7.0)するようにしました。

また、コンポーネントを実行するには features で component-model を指定する必要がありました。

run_component/Cargo.toml
[package]
name = "run_component"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1"
wasmtime = { git = 'https://github.com/bytecodealliance/wasmtime', features = ['component-model'] }

wit_bindgen と同様に wasmtime::component::bindgen! マクロで WIT ファイルに基づいたコードを生成してくれるので、それを使って実装します。

こちらもデフォルトでは ./wit ディレクトリを参照するようになっていますが、in を使って任意のディレクトリを指定できます。

コンポーネントを実行するには Config の wasm_component_model を true に設定します。

run_component/src/main.rs
use anyhow::Result;
use wasmtime::component::{Component, Linker};
use wasmtime::{Config, Engine, Store};

use std::env;

wasmtime::component::bindgen!("sample" in "../sample/wit");

#[derive(Default)]
struct State {}

// コンポーネントがインポートする関数の実装
impl SampleImports for State {
    fn log(&mut self, level: LogLevel, msg: String) -> anyhow::Result<()> {
        println!("[{:?}] {}", level, msg);
        Ok(())
    }

    fn getitem(&mut self, id: String,) -> wasmtime::Result<Item> {
        Ok(Item { id: id.clone(), value: 123 })
    }
}

fn main() -> Result<()> {
    let file = env::args().skip(1).next().unwrap_or_default();

    let mut config = Config::new();
    // コンポーネントの有効化
    config.wasm_component_model(true);

    let engine = Engine::new(&config)?;

    let component = Component::from_file(&engine, file)?;

    let mut linker = Linker::new(&engine);

    Sample::add_to_linker(&mut linker, |s: &mut State| s)?;

    let mut store = Store::new(&engine, State::default());

    let (bindings, _) = Sample::instantiate(&mut store, &component, &linker)?;
    // コンポーネントの run 関数呼び出し
    bindings.call_run(&mut store)?;

    Ok(())
}

(a) で作成したコンポーネントを実行すると次のようになりました。

実行例
$ cargo run ../sample/sample-component.wasm
[LogLevel::Debug] called run
[LogLevel::Info] item: Item { id: "item-1", value: 123 }
8
2
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
8
2