はじめに
前回は Go で Wasm を動かしました。今回は Rust で同じことをやってみます。
Rust は WebAssembly との相性が非常に良く、公式でも積極的にサポートされています。
Go 編と比較しながら読んでいただくと、WASI の理解がより深まると思います。
環境
- Oracle Linux 9
- Rust(rustup でインストール)
- Wasmtime(Wasm ランタイム)
① Rust のインストール
Rust は公式の rustup を使ってインストールします。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
インストール後、パスを通します。
source $HOME/.cargo/env
下記コマンドで確認します。
$ rustc --version
rustc 1.xx.x (xxxxxxx 20xx-xx-xx)
② Wasm ターゲットの追加
Rust で Wasm 向けバイナリを作るには、Wasm専用のビルドターゲット(どこで動かす用に作るか)という出力形式の指定を追加する必要があります。
rustup target add wasm32-wasip1
wasm32-wasip1 って・・・?
| コマンド | 意味 |
|---|---|
wasm32 |
WebAssembly(32bit)形式で出力する |
wasip1 |
WASI Preview1 という仕様に準拠する |
つまり、WASIで動くWebAssembly形式で出力するという意味になります。
Go 編での GOOS=wasip1 GOARCH=wasm と同じ意味を持ちます。
確認配下コマンドで行います。
$ rustup target list --installed
wasm32-wasip1
リストに表示されていればOKです。
③ Wasmtime のインストール
Wasmtime は .wasm ファイルを実行するためのエンジンのようなものです。
curl https://wasmtime.dev/install.sh -sSf | bash
すぐ使いたい場合はパスを手動で通します。(私は通します)
export PATH="$HOME/.wasmtime/bin:$PATH"
確認は下記コマンドでします。
$ wasmtime --version
wasmtime 42.0.0 (aa3cfe72f 2026-02-24)
④ プロジェクトを作成する
Cargo(Rust のパッケージマネージャ)でプロジェクトを作成します。
cargo new hello_wasi
cd hello_wasi
ディレクトリ構成は下記のようになっているはずです。
hello_wasi/
├── Cargo.toml
└── src/
└── main.rs
⑤ Rust・WASI・Wasmtime の関係を理解する
コードを書く前に、登場人物の関係を整理しておきます。
ここを理解しておくと、後でエラーが出たときに原因が即座にわかります。
まとめると、それぞれの役割はこうなります。
| レイヤー | 役割 | 今回の該当 |
|---|---|---|
| Rust ソースコード | 「何をしたいか」を書く | src/main.rs |
| WASI | Wasmが外部とやり取りするためのインタフェース | wasm32-wasip1 |
| Wasm バイナリ | WASI 仕様に沿って作られた実行ファイル | hello_wasi.wasm |
| Wasmtime | Wasm を受け取り、WASI の命令を OS に橋渡しするエンジン |
wasmtime コマンド |
⑥ Hello World を動かす
src/main.rs を以下の内容にします。
fn main() {
println!("Hello from Rust WASM!");
}
ビルドは下記コマンドで行います。
cargo build --target wasm32-wasip1 --release
実行します。
wasmtime target/wasm32-wasip1/release/hello_wasi.wasm
出力:
Hello from Rust WASM!
Rust で書いたコードが、Linux のネイティブバイナリではなく Wasm として実行されています。
cargo build に --target wasm32-wasip1 を渡すだけで出力先が .wasm に切り替わる点が Rust のシンプルなところです。
Go の場合は環境変数を2つ並べる必要がありましたが、Rust はオプション1つで済みます。
⑦ ファイル読み込みを試す
次に、テキストファイルを読み込む処理を追加します。
ここから WASI のサンドボックスと向き合う場面になります。
src/main.rs を書き換えます。
use std::fs;
fn main() {
let data = fs::read_to_string("test.txt").expect("ファイルが読めませんでした");
println!("{}", data);
}
読み込む用のファイルを作成します。
echo "WASI is cool" > test.txt
ビルドしていきます。
cargo build --target wasm32-wasip1 --release
下記でそのまま実行してみます。
wasmtime target/wasm32-wasip1/release/hello_wasi.wasm
出力結果
thread 'main' panicked at 'ファイルが読めませんでした: ...'
エラーになります。コードに間違いはありません。
これは Wasm のセキュリティモデルとして、サンドボックスとして動くためです。
悪意のあるコードがシステム上のファイルを勝手に読み書きできないよう、 アクセスできるリソースをすべて実行者が明示的に許可する設計になっています。
⑨ --dir でディレクトリを許可する
解決方法は、実行時に --dir オプションでアクセスを許可することです:
wasmtime --dir=. target/wasm32-wasip1/release/hello_wasi.wasm
--dir=. の意味は「カレントディレクトリ(.)を Wasm に見せることを許可する」です。
出力は下記のようになります。
WASI is cool
--dir は複数指定することもできます。たとえば /data と /config を別々に許可するといった細かい制御が可能です。
⑩ Go 編との比較
| Go | Rust | |
|---|---|---|
| ビルドコマンド | GOOS=wasip1 GOARCH=wasm go build |
cargo build --target wasm32-wasip1 |
| ターゲット指定 | 環境変数で指定 |
--target オプションで指定 |
| ファイルアクセス |
--dir=. が必要 |
--dir=. が必要 |
| 出力ファイル | main.wasm |
target/wasm32-wasip1/release/xxx.wasm |
ファイルアクセスの制限は言語に関係なく WASI の仕様なので、Go でも Rust でも同じ --dir=. が必要になります。言語が違っても同じ制限にぶつかる、これが WASI の「標準化」としての意味です。
まとめ
| ステップ | コマンド |
|---|---|
| Rust インストール | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
| Wasm ターゲット追加 | rustup target add wasm32-wasip1 |
| Wasm ビルド | cargo build --target wasm32-wasip1 --release |
| 実行(基本) | wasmtime target/wasm32-wasip1/release/hello_wasi.wasm |
| 実行(ファイルアクセスあり) | wasmtime --dir=. target/wasm32-wasip1/release/hello_wasi.wasm |
Rust のコードは普通の Rust のまま書けばよく、「WASI 向けに何か特別な書き方をする」必要はありません。WASI を意識するのはビルド時のターゲット指定と、実行時の --dir オプションだけです。この分離がうまく設計されているおかげで、既存の Rust コードを Wasm に移植するコストが低く抑えられています。