Catching Panics
By default, when a Rust function exported to JavaScript panics, Rust will abort
and any allocated resources will be leaked. If you build with with
-Cpanic=unwind and the std feature, Rust panics will be caught at the
JavaScript boundary and converted into JavaScript exceptions, allowing proper
cleanup and error handling.
When enabled, panics in exported Rust functions are caught and thrown as
PanicError exceptions in JavaScript. For async functions, the returned promise
is rejected with the PanicError.
Requirements
panic=unwind- The panic strategy must be set tounwind(notabort)-Zbuild-std- Required to rebuild the standard library with theunwindpanic strategy- Rust nightly compiler -
-Zbuild-stdis only available on nightly stdfeature -stdsupport is required to usestd::panic::catch_unwind. to catch panics.
Building
Build your project with the required flags:
RUSTFLAGS="-Cpanic=unwind" cargo +nightly build --target wasm32-unknown-unknown -Zbuild-std=std,panic_unwind
Or set these in .cargo/config.toml:
[unstable]
build-std = ["std", "panic_unwind"]
[build]
target = "wasm32-unknown-unknown"
rustflags = ["-C", "panic=unwind"]
[profile.release]
panic = "unwind"
Then build with:
cargo +nightly build
How It Works
Synchronous Functions
When a synchronous exported function panics, the panic is caught and a
PanicError is thrown to JavaScript:
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn divide(a: i32, b: i32) -> i32 { if b == 0 { panic!("division by zero"); } a / b } }
import { divide } from './my_wasm_module.js';
try {
divide(10, 0);
} catch (e) {
console.log(e.name); // "PanicError"
console.log(e.message); // "division by zero"
}
Async Functions
For async functions, panics cause the returned promise to be rejected with a
PanicError:
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; #[wasm_bindgen] pub async fn fetch_data(url: String) -> Result<JsValue, JsValue> { if url.is_empty() { panic!("URL cannot be empty"); } // ... fetch implementation Ok(JsValue::NULL) } }
import { fetch_data } from './my_wasm_module.js';
try {
await fetch_data("");
} catch (e) {
console.log(e.name); // "PanicError"
console.log(e.message); // "URL cannot be empty"
}
Closures
Rust closures passed to JavaScript with Closure::new, Closure::wrap, and
Closure::once also catch panics when built with panic=unwind. When a panic
occurs inside a closure invoked from JavaScript, the panic is caught and thrown
as a PanicError exception.
Like exported functions, catching panics in closures requires the closure to
satisfy the UnwindSafe trait.
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { fn setCallback(f: &Closure<dyn FnMut()>); } let closure = Closure::new(|| { panic!("closure panicked!"); }); setCallback(&closure); }
try {
triggerCallback();
} catch (e) {
console.log(e.name); // "PanicError"
console.log(e.message); // "closure panicked!"
}
For closures that should not catch panics (and abort the program instead), use
the *_aborting variants: new_aborting, wrap_aborting, once_aborting, and
once_into_js_aborting. These do not require UnwindSafe.
See Passing Rust Closures to JavaScript for
more details on closure panic handling and the UnwindSafe requirement.
The PanicError Class
When a panic occurs, a PanicError JavaScript exception is created with:
nameproperty set to"PanicError"messageproperty containing the panic message (if the panic was created with a string message)
If the panic payload is not a String or &str (e.g., panic_any(42)), the message will
be "No panic message available".
Limitations
Nightly Only
This feature requires a nightly Rust compiler and will not work on stable Rust.
UnwindSafe Requirement
All function arguments must satisfy Rust's UnwindSafe trait. This is
automatically handled by wrapping arguments in AssertUnwindSafe, but be aware
that this assumes your code handles potential inconsistent state after a panic.
Mutable Slice Arguments
Functions with &mut [T] slice arguments cannot be used because mutable slices
are not UnwindSafe. Consider using owned types like Vec<T> instead.
See Also
catchattribute - For catching JavaScript exceptions in RustResult<T, E>type - For explicit error handling between Rust and JavaScript