Cloudflare Worker 一般用 JavaScript/TypeScript 写,但官方的 workers-rs 让我们可以用 Rust 写,再编译成 WASM 运行在 Worker 运行时上。这里记录一个最简项目的结构和构建链路。

项目结构

一个 workers-rs 项目的核心文件只有三个:

  • Cargo.toml —— Rust 包描述
  • wrangler.toml —— Cloudflare Worker 配置
  • src/lib.rs —— 入口(注意是 lib.rs 而不是 main.rs

入口代码

入口用 #[event(fetch)] 宏标注一个 async 函数,签名固定为 (Request, Env, Context) -> Result<Response>

use worker::*;

#[event(fetch)]
async fn fetch(_req: Request, _env: Env, _ctx: Context) -> Result<Response> {
    Response::ok("Hello World!")
}

如果要做定时任务(cron),则再加一个 #[event(scheduled)] 标注的函数:

#[event(scheduled)]
async fn cron_task(event: ScheduledEvent, env: Env, ctx: ScheduleContext) {
    console_log!("Event: {:?}", event);
}

Cargo.toml 的关键点

最重要的是把 crate 类型设成 cdylib,这样才能编译出供 WASM 链接的动态库:

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

[dependencies]
worker = { version = "0.8" }
worker-macros = { version = "0.8" }

构建链路

这是 workers-rs 和普通 Worker 最不一样的地方。Rust 代码不是直接跑,而是经过一条编译流水线:

Rust 源码 → WASM(wasm-bindgen / wasm-opt)→ worker-build 打包成 build/index.js

wrangler.toml 里通过 [build] 把这条链子接起来,main 指向打包产物 build/index.js

name = "my-worker"
main = "build/index.js"
compatibility_date = "2026-06-18"

[build]
command = "cargo install -q worker-build && worker-build --release"

上面是 cargo generate 生成的默认配置。我自己又在 command 前面加了一段 export

[build]
command = "export WASM_BINDGEN_BIN=$(which wasm-bindgen) WASM_OPT_BIN=$(which wasm-opt) && cargo install -q \"worker-build@^0.8\" && worker-build --release"

作用是让 worker-build 复用系统(或 mise)里已装好的 wasm-bindgenwasm-opt,而不是让它自己去下载一份,避免下载慢或版本不匹配的问题。

worker-build --release 跑完后,build/ 目录里会生成:

  • index.js —— JS shim(约 20KB),Worker 实际加载的入口
  • index_bg.wasm —— 编译出的 WASM(约 330KB),真正的业务逻辑

也就是说,部署到 Cloudflare 的本质上还是一段 JS(shim)+ 一个 WASM 模块,Rust 只是写法。

小结

用 Rust 写 Worker 的心智模型:照常写 Rust,借 worker crate 拿到 Request/Response/Env 等运行时能力,编译成 cdylib,再由 worker-build 把 WASM 包成 JS shim 部署。配置上记住三件事——入口是 lib.rs、crate 类型是 cdylibwrangler.tomlmain 指向 build/index.js