Rust Worker panics no longer poison the whole instance!
Hey there, it's Shiichan! Today's topic is a little deep in the weeds, but it's a big deal for reliability. When your Rust Worker panics, it no longer takes everything down with it. I'm excited!
Cloudflare Blog
What was announced?
The Cloudflare Blog announced a mechanism that makes Rust Workers much more reliable. The theme is "recovering from panics and aborts." They worked on wasm-bindgen, the foundation that turns Rust into WebAssembly (Wasm), so that a panic no longer corrupts the WebAssembly instance.
The story so far
Until now, a panic in a Rust Worker was pretty serious. The wasm32-unknown-unknown target defaults to panic=abort, so a panic traps with an unreachable instruction and exits back to JavaScript as a WebAssembly.RuntimeError. Worse, the instance's state gets corrupted and can't be recovered.
The scariest part is that one request's failure could cascade to other requests running in the same instance, or even to new incoming ones. Cloudflare's first fix wrapped things in a JavaScript Proxy and rebuilt the module after a failure, but that meant in-memory state was lost on every reinitialization. That's rough for stateful workloads like Durable Objects.
What changes
With the new mechanism, a panic now surfaces properly as a JavaScript exception (PanicError). So you can catch and handle it, and for an async export the returned Promise is rejected with PanicError.
Even better, Rust destructors run correctly, and the WebAssembly instance stays valid and reusable! That means you recover without losing in-memory state. For stateful Durable Objects, that's an especially nice win.
Dive Deep
The key was the WebAssembly Exception Handling feature, which gained wide engine support in 2023. Using it, they implemented panic=unwind even for wasm32-unknown-unknown.
The build rebuilds the standard library with unwind support, like this:
RUSTFLAGS='-Cpanic=unwind' cargo build -Zbuild-std
When unwinding, the compiler generates WebAssembly try / catch blocks so destructors run even if a panic happens mid-way. It looks roughly like this:
try
call imported_func
catch_all
call drop_b
call drop_a
rethrow
end
wasm-bindgen itself needed a bunch of changes too. They taught the Walrus WebAssembly parser to understand try / catch, marked exports as extern "C-unwind" so unwinding can cross the Rust-JavaScript boundary, and added logic that catches panics at the boundary and surfaces them as PanicError. The details are collected in the Catching Panics guide.
Aborts still remain, though. An abort like out-of-memory (OOM) can't unwind, so state can't be recovered, but they made it detectable so it doesn't cascade into later work. Using Exception.Tag, they distinguish a "recoverable panic" from a "non-recoverable abort," and they added a set_on_abort hook you can attach at initialization plus reentrancy guards for deeply interleaved calls. That side is covered in the Handling Aborts guide.
For users, it's enabled by adding a --panic-unwind flag at build time in workers-rs 0.8.0 and later (the setup instructions are here). Note that it currently needs an experimental nightly target, and work to bring it to stable is ongoing. Closures that capture references incompatible with unwinding need care, like using Closure::new_aborting.
Wrap-up
- Rust Worker panics can now recover as a JavaScript PanicError without corrupting the whole instance
- Using WebAssembly Exception Handling, they implemented
panic=unwind; destructors run correctly and in-memory state is preserved - Aborts (like OOM) are distinguished via Exception.Tag to prevent cascades, and a
set_on_aborthook was added - To use it, enable
--panic-unwindin workers-rs 0.8.0+; it currently needs nightly, with stabilization in progress - Perfect for anyone writing Workers or Durable Objects in Rust who wants to push reliability to the limit!