wasm_safe_thread

A std::thread replacement for wasm32 with proper async integration.
This crate provides a unified threading API that works across both WebAssembly and native platforms. Unlike similar crates, it's designed from the ground up to handle the async realities of browser environments.
Comparison with wasm_thread
wasm_thread is a popular crate that aims to closely replicate std::thread on wasm targets. This section compares design goals and practical tradeoffs.
Design goals
wasm_safe_thread: async-first, unified API that works identically on native and wasm32, playing well with the browser event loop.wasm_thread: highstd::threadcompatibility with minimal changes to existing codebases (wasm32 only; native usesstd::threaddirectly).
Feature comparison
| Feature | wasm_safe_thread | wasm_thread |
|---|---|---|
| Native support | Unified API (same code runs on native and wasm) | Re-exports std::thread::* on native |
| Node.js support | Yes, via worker_threads |
Browser only |
| Event loop integration | yield_to_event_loop_async() for cooperative scheduling |
No equivalent |
| Spawn hooks | Global hooks that run at thread start | Not available |
| Parking primitives | park()/unpark() on wasm workers |
Not implemented |
| Scoped threads | Not implemented | scope() allows borrowing non-'static data |
| std compatibility | Custom Thread/ThreadId (similar API) |
Re-exports std::thread::{Thread, ThreadId} |
| Worker scripts | Inline JS via wasm_bindgen(inline_js) |
External JS files; es_modules feature for module workers |
| wasm-pack targets | ES modules (web) only |
web and no-modules via feature flag |
| Dependencies | wasm-bindgen, js-sys, wasm_safe_mutex | web-sys (many features), futures crate |
| Thread handle | thread() returns &Thread |
thread() is unimplemented (panics) |
Shared capabilities
Both crates provide:
spawn()andBuilderfor thread creationjoin()(blocking) andjoin_async()(async) for waiting on threadsis_finished()for non-blocking completion checks- Thread naming via
Builder::name()
Behavioral differences to know
- Main-thread blocking: both crates must avoid blocking APIs on the browser main thread;
join_async()is the safe path. - Spawn timing: wasm workers only run after the main thread yields back to the event loop.
- Worker spawning model:
wasm_threadproxies worker spawning through the main thread;wasm_safe_threadspawns directly (simpler, but different model).
Implementation differences (for maintainers)
Result passing:
wasm_safe_threaduseswasm_safe_mutex::mpscchannels with asyncrecv_async()wasm_threadusesArc<Packet<UnsafeCell>>with a customSignalprimitive andWakerlist
Async waiting:
wasm_safe_threadwraps JavaScript Promises viawasm-bindgen-futures::JsFuturewasm_threadimplementsfutures::future::poll_fnwith manualWakertracking
When to use which
Choose wasm_safe_thread when:
- You need Node.js support (wasm_thread is browser-only)
- You want identical behavior on native and wasm (e.g., for testing)
- You need park/unpark synchronization primitives
- You need spawn hooks for initialization (logging, tracing, etc.)
- You prefer fewer dependencies and no external JS files
- You want an actively developed library with responsive issue/PR handling
Choose wasm_thread when:
- You need scoped threads for borrowing non-
'staticdata - You want maximum compatibility with
std::threadtypes - You need
no-moduleswasm-pack target support
Usage
Replace use std::thread with use wasm_safe_thread as thread:
use wasm_safe_thread as thread;
// Spawn a thread
let handle = spawn;
// Wait for the thread to complete
let result = handle.join.unwrap;
assert_eq!;
API
Thread spawning
use ;
// Simple spawn
let handle = spawn;
// Convenience function for named threads
let handle = spawn_named.unwrap;
// Builder pattern for more options
let handle = new
.name
.spawn
.unwrap;
Joining threads
use spawn;
// Synchronous join (works on native and wasm worker threads)
let handle = spawn;
let result = handle.join.unwrap;
assert_eq!;
// Non-blocking check
let handle = spawn;
if handle.is_finished
For async contexts, use join_async:
// In an async context (e.g., with wasm_bindgen_futures::spawn_local)
let result = handle.join_async.await.unwrap;
Thread operations
use ;
use Duration;
// Get current thread
let thread = current;
println!;
// Sleep
sleep;
// Yield to scheduler
yield_now;
Park/unpark works from background threads:
use ;
use Duration;
let handle = spawn;
handle.thread.unpark; // Wake parked thread
handle.join.unwrap; // join() requires worker context on wasm
Event loop integration
use yield_to_event_loop_async;
// Yield to browser event loop (works on native too)
yield_to_event_loop_async.await;
Thread local storage
use thread_local;
use RefCell;
thread_local!
COUNTER.with;
Spawn hooks
Register callbacks that run when any thread starts:
use ;
// Register a hook
register_spawn_hook;
// Hooks run in registration order, before the thread's main function
// Remove specific hook
remove_spawn_hook;
// Clear all hooks
clear_spawn_hooks;
Async task tracking (WASM)
When spawning async tasks inside a worker thread using wasm_bindgen_futures::spawn_local,
you must notify the runtime so the worker waits for tasks to complete before exiting:
use ;
task_begin;
spawn_local;
These functions are no-ops on native platforms, so you can use them unconditionally in cross-platform code.
WASM Limitations
Main thread restrictions
The browser main thread cannot use blocking APIs:
join()- Usejoin_async().awaitinsteadpark()/park_timeout()- Only works from background threadsMutex::lock()from std - Usewasm_safe_mutexinstead
SharedArrayBuffer requirements
Threading requires SharedArrayBuffer, which needs these HTTP headers:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
See Mozilla's documentation for details.
Environment support
- Browser: Web Workers with shared memory
- Node.js: worker_threads module
Building for WASM
Standard library must be rebuilt with atomics support:
# Install nightly and components
# Build with atomics
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory' \
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.