在做 wgpu Game of Life 時發現一件事:WASM 環境下的 Rust async 不需要 Tokio 或 async-std——瀏覽器的 event loop 就是 runtime

在 native Rust,你需要一個 executor 來 poll futures。但在 WASM,wasm-bindgen-futures 把 Rust 的 Future 轉換成 JavaScript 的 Promise,交給瀏覽器的 event loop 來驅動:

#[wasm_bindgen]
pub async fn start(canvas_id: &str) {
    // 每個 .await 都是把控制權交還給瀏覽器的 event loop
    let adapter = instance.request_adapter(&options).await;
    let (device, queue) = adapter.request_device(&desc, None).await;
}

JavaScript 端看到的就是一個回傳 Promise 的函式:

await wasm.start("canvas");

每次 .await 時,Rust future 暫停執行,控制權回到瀏覽器。當底層的 JS 操作(例如 WebGPU 的 requestAdapter)完成時,瀏覽器透過 microtask 觸發 Rust 的 waker,從暫停處繼續執行。

Native WASM
Runtime Tokio / async-std 瀏覽器 event loop
Executor Rust 端的 thread pool JS microtask queue
Spawn tokio::spawn(多執行緒) spawn_local(單執行緒)

所以 Cargo.toml 只需要 wasm-bindgen-futures,不需要任何 Rust async runtime。