Expand description
A unified cross-platform std::thread + std::sync replacement for native + wasm32.

This crate provides a unified threading API and synchronization primitives that work across both
WebAssembly and native platforms. In practice, you can treat it as a cross-platform replacement
for much of std::thread plus key std::sync primitives. Unlike similar crates, it’s designed
from the ground up to handle the async realities of browser environments.
§Synchronization primitives
Alongside thread APIs, this crate includes WebAssembly-safe synchronization primitives:
These APIs are usable on their own; you do not need to spawn threads with this crate to use
Mutex, RwLock, Condvar, or mpsc.
These primitives adapt their behavior to the runtime:
- Native: uses thread parking for efficient blocking
- WASM worker: uses
Atomics.wait-based blocking when available - WASM main thread: falls back to non-blocking/spin strategies to avoid panics
§Sync Example
use wasm_safe_thread::Mutex;
let data = Mutex::new(41);
*data.lock_sync() += 1;
assert_eq!(*data.lock_sync(), 42);§Channel Example
use wasm_safe_thread::mpsc::channel;
let (tx, rx) = channel();
tx.send_sync(5).unwrap();
assert_eq!(rx.recv_sync().unwrap(), 5);§Threading primitives
In addition to synchronization primitives, this crate provides a std::thread-like API:
spawn(), Builder, JoinHandle, park(), Thread::unpark(), thread locals,
and spawn hooks.
§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()/Thread::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, continue | web-sys (many features), futures crate |
| Thread handle | JoinHandle::thread() returns &Thread | thread() is unimplemented (panics) |
§Shared capabilities
Both crates provide:
spawn()andBuilderfor thread creationJoinHandle::join()(blocking) andJoinHandle::join_async()(async) for waiting on threadsJoinHandle::is_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;
JoinHandle::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_threaduses its built-inmpscchannels 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 = thread::spawn(|| {
println!("Hello from a worker!");
42
});
// Wait for the thread to complete
// Synchronous join (works on native and some browser context - but not reliably!)
let result = handle.join().unwrap();
assert_eq!(result, 42);§API
§Thread spawning
use wasm_safe_thread::{spawn, spawn_named, Builder};
// Simple spawn
let handle = spawn(|| "result");
// Convenience function for named threads
let handle = spawn_named("my-worker", || "result").unwrap();
// Builder pattern for more options
let handle = Builder::new()
.name("my-worker".to_string())
.spawn(|| "result")
.unwrap();§Joining threads
use wasm_safe_thread::spawn;
// Synchronous join (works on native and some browser context - but not reliably!)
let handle = spawn(|| 42);
let result = handle.join().unwrap();
assert_eq!(result, 42);
// Non-blocking check
let handle = spawn(|| 42);
if handle.is_finished() {
// Thread completed
}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 wasm_safe_thread::{current, sleep, yield_now};
use std::time::Duration;
// Get current thread
let thread = current();
println!("Thread: {:?}", thread.name());
// Sleep
sleep(Duration::from_millis(10));
// Yield to scheduler
yield_now();Park/unpark works from background threads:
if cfg!(target_arch="wasm32") { return } //join not reliable on wasm
use wasm_safe_thread::{spawn, park, park_timeout};
use std::time::Duration;
let handle = spawn(|| {
// Park/unpark (from background threads)
park_timeout(Duration::from_millis(10)); // Wait with timeout
});
handle.thread().unpark(); // Wake parked thread
handle.join().unwrap(); // join() is not reliable on wasm and should be avoided§Event loop integration
use wasm_safe_thread::yield_to_event_loop_async;
// Yield to browser event loop (works on native too)
yield_to_event_loop_async().await;§Thread local storage
use wasm_safe_thread::thread_local;
use std::cell::RefCell;
thread_local! {
static COUNTER: RefCell<u32> = RefCell::new(0);
}
COUNTER.with(|c| {
*c.borrow_mut() += 1;
});§Spawn hooks
Register callbacks that run when any thread starts:
use wasm_safe_thread::{register_spawn_hook, remove_spawn_hook, clear_spawn_hooks};
// Register a hook
register_spawn_hook("my-hook", || {
println!("Thread starting!");
});
// Hooks run in registration order, before the thread's main function
// Remove specific hook
remove_spawn_hook("my-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 wasm_safe_thread::{task_begin, task_finished};
task_begin();
wasm_bindgen_futures::spawn_local(async {
// ... async work ...
task_finished();
});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:
JoinHandle::join()- UseJoinHandle::join_async()insteadpark()/park_timeout()- Only works from background threadsMutex::lock()from std - Usewasm_safe_thread::Mutexinstead
§SharedArrayBuffer requirements
Threading requires SharedArrayBuffer, which needs these HTTP headers:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corpSee 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
rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
# Build with atomics
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory' \
cargo +nightly build -Z build-std=std,panic_abort \
--target wasm32-unknown-unknownRe-exports§
pub use guard::Guard;pub use mutex::Mutex;pub use mutex::NotAvailable;
Modules§
- condvar
- A WebAssembly-safe condition variable implementation.
- guard
- Guard types for mutex and rwlock locks.
- mpsc
- A multi-producer, single-consumer FIFO queue communication primitive.
- mutex
- A mutual exclusion primitive that works across native and WebAssembly targets.
- rwlock
- A WebAssembly-safe read-write lock that papers over platform-specific locking constraints.
- spinlock
- A simple spinlock implementation for short-lived critical sections.
Macros§
- async_
test - thread_
local - Declare a new thread local storage key of type
LocalKey.
Structs§
- Access
Error - An error returned by
LocalKey::try_with. - Builder
- A builder for configuring and spawning threads.
- Join
Handle - A handle to a thread.
- Local
Key - A thread local storage key which owns its contents.
- Thread
- A handle to a thread.
- Thread
Id - A unique identifier for a running thread.
Functions§
- available_
parallelism - Returns an estimate of the default amount of parallelism a program should use.
- clear_
spawn_ hooks - Removes all registered spawn hooks.
- current
- Gets a handle to the thread that invokes it.
- install_
println_ eprintln_ console_ hook - Installs a global spawn hook that redirects
println!/eprintln!to JavaScript console output. - park
- Blocks unless or until the current thread’s token is made available.
- park_
timeout - Blocks unless or until the current thread’s token is made available or the specified duration has been reached.
- redirect_
println_ eprintln_ to_ console_ current_ thread - Redirects
println!/eprintln!for the current thread to JavaScript console output. - register_
spawn_ hook - Registers a global spawn hook with the given name.
- remove_
spawn_ hook - Removes a spawn hook by name.
- sleep
- Puts the current thread to sleep for at least the specified duration.
- spawn
- Spawns a new thread, returning a JoinHandle for it.
- spawn_
named - A convenience function for spawning a thread with a name.
- task_
begin - No-op on native - async task tracking is only needed on WASM.
- task_
finished - No-op on native - async task tracking is only needed on WASM.
- yield_
now - Cooperatively gives up a timeslice to the OS scheduler.
- yield_
to_ event_ loop_ async - Yields to the async executor, allowing other tasks to run.