この記事は ひとりCloudflareを使い倒す Advent Calendar 2025 の17日目です
年の瀬、忘年会とかが特にあるわけでもなく「税制変わったな~」と思うくらいの @toreis です。
先日 Cloudflare Workers の記事でこんなことを言及しました。
詳細は他の記事を参照いただきたいですが、WebAssembly はいろんな言語で書いたものをコンパイルして WebAssemblyにするので、例えば C, C++, C#, Rust, Go, Kotlin, etc...で書いたコードも WebAssembly になります。
つまり、V8 で動かせて怪しくないアプリなら何でも Workers で動かせるのです。やったぁ!
そして、Rust をちゃんと触ったことがない私は思ったのです。
「Workers を Rust で書いてみたい」と。
ということで、Todo リストを作ってると明日が来ちゃうので、どういう仕組みで動いているのかを理解します。
とりあえず Hello, World!
Workers を Rust で作ってみます。なお、Rustup は入っている前提です。
まずは target を設定します (Workers で動かすための設定) 。
rustup target add wasm32-unknown-unknown
そして、GitHub からテンプレを引っ張ってくるために cargo-generate を入れます。
cargo install cargo-generate
準備できました。
そしたら Clone してきます。
cargo generate cloudflare/workers-rs
そうするとどのテンプレートを使うか聞いてくれるので、ここでは templates/hello-world を使います。
Favorite `cloudflare/workers-rs` not found in config, using it as a git repository: https://github.com/cloudflare/workers-rs.git
? Which template should be expanded? ›
templates\axum
❯ templates\hello-world
templates\hello-world-http
templates\leptos
Project Name は適当に設定します。
そしたらば、Project Name と同名のディレクトリの中にソースファイルなどが一式詰まってプロジェクト出来上がりです。
src/lib.rs を見てみると、いかにも Hello, World! を返すコードが見られます。
use worker::*;
#[event(fetch)]
async fn fetch(
_req: Request,
_env: Env,
_ctx: Context,
) -> Result<Response> {
Response::ok("Hello World!")
}
さて、プロジェクトをビルドして実行する前に、wrangler.toml をいじります。
name = "hello-workers-rs"
main = "build/index.js"
compatibility_date = "2025-12-21"
[build]
- command = "cargo install -q worker-build@^0.7 && worker-build --release"
+ command = "cargo install -q worker-build@^0.7.2 && worker-build --release"
パッチバージョンが書いておらずバージョン解析に失敗するので、今日時点で最新の 0.7.2 にしておきます。
書き込んだら、npx wrangler dev で実行してみます。
ブワーッとビルドして、build ディレクトリに成果物が詰まっていることが確認できます。
その中に index.js もあります。これが Workers の実行ファイルになりそうですね。
ビルドが終わると、ローカルサーバーが実行されて http://127.0.0.1:8787 で待機します。
アクセスしてみると、この通り。
今日びよく見かける Hello, World! です。
いや、何が起きてん
Rust のコードが動いています。謎です。V8 て TypeScript エンジンちゃうんか。
これについては、ビルド成果物を見てみるとしましょう。
build ディレクトリには、Workers のエントリポイントである index.js、Rust でつくったやつのコンパイル結果と思われる index_bg.wasm が見られます。
import{WorkerEntrypoint as ct}from"cloudflare:workers";import D from"./index_bg.wasm";
function q(){s++,p=null,F=null,typeof numBytesDecoded<"u"&&(numBytesDecoded=0),typeof g<"u"&&(g=0),r=new WebAssembly.Instance(D,$).exports,r.__wbindgen_start()}
後者のコードブロックに注目してください。
r=new WebAssembly.Instance(D,$).exports
という記述、これは WebAssembly Instance を作るという、紛れもない Wasm の実行始めです。
そしてトドメが
r.__wbindgen_start()
です。
Wasm 動かしてらぁ!です。
そして、実際には r という変数にはインスタンスの exports、すなわち Wasm がエクスポートしてる関数や変数群が代入されていて、そのうちの __wbindgen_start() が実行されています。
名前からしていかにもですね。
で、実際に fetch されるときには、以下のコードが動いているのではないか、と推測します。
function B(t,e,n){return r.fetch(t,e,n)}
var z=class extends ct{}
z.prototype.fetch=function(e){return B.call(this,e,this.env,this.ctx)}
r.fetch() は Wasm で定義した Fetch を呼ぶための関数と思われます。
そしてそれを B でラップしたあと、z (WorkerEntrypoint) の fetch を実行するのに必要な情報を追加して呼び出します。(多分)
おわりに
Rust が Wasm になって実行されているということがわかりました。
動く仕組みについても、浅い理解なりに追うことができました。
何が嬉しいねん
さて、Workers を Rust で書くことの嬉しさってなんなんでしょう。
まあまずは Rust にあって TypeScript にないものなーんだ?って話が有力です。
所有権とかもろもろありますが、私の浅い理解だと「Rust 使っときゃとにかく安全で高速」というイメージがあります。
Linux カーネルにも組まれるくらいには安全なのでしょう、多分。
実際、とある案件で Rust を使っていたとき、挙がってくるバグレポートはフロントばっか、バックはレポート 0 ですげーと思った記憶があります。
また、CPU マターの処理はいろんな言語と比較して高速ですので、例えば動画のエンコードとかも Workers に乗るかもしれませんね!
(爆発オチ)
