Embedding RUSM as a library
The app model is how most projects use RUSM. But the runtime is also a plain Rust library you can drive directly from your own binary — useful when RUSM is one piece of a larger app. Add the crates:
cargo add rusm-otp # the Wasm-free actor core
cargo add rusm-wasm # + the Wasmtime backend, to host componentsThe OTP core, without any Wasm
RUSM's heart is a Wasm-free Erlang/OTP actor library, rusm-otp — real lightweight processes, message passing, links, monitors, supervision, a registry, and timers, with no WebAssembly at all (the dependency graph guarantees it stands alone):
use rusm_otp::{ExitReason, Received, Runtime};
#[tokio::main]
async fn main() {
let rt = Runtime::new();
// A worker: receive one message, then exit.
let worker = rt.spawn(|mut ctx| async move {
if let Received::Message(bytes) = ctx.recv().await {
println!("worker got {} bytes", bytes.len());
}
});
// Supervise it: monitor delivers a `Down` with the exit reason.
let (tx, rx) = tokio::sync::oneshot::channel();
let watcher = rt
.spawn(move |mut ctx| async move {
if let Received::Down { reason, .. } = ctx.recv().await {
let _ = tx.send(reason);
}
})
.pid();
rt.monitor(watcher, worker.pid());
rt.send(worker.pid(), b"hello".to_vec()); // messages are bytes (Vec<u8>)
assert_eq!(rx.await.unwrap(), ExitReason::Normal);
}You also get spawn_link (crash propagation), trap_exit, a named registry (register/whereis), timers (send_after/cancel), graceful shutdown, and TCP (listen/connect, one process per connection) — all in rusm-otp, all without touching Wasm. See links & supervision.
Hosting a compiled component
Add the rusm-wasm backend and host a prebuilt component as a process. A WasmRuntime wraps an rusm-otp Runtime; construct it inside a Tokio runtime (it starts the epoch ticker):
use rusm_otp::Runtime;
use rusm_wasm::{Capabilities, WasmRuntime};
#[tokio::main]
async fn main() {
let rt = Runtime::new();
let wasm = WasmRuntime::new(rt.clone()).unwrap();
// compile once → prepare once (imports + entry export resolved) → spawn many.
let bytes = std::fs::read("wasm/worker.wasm").unwrap();
let prepared = wasm
.prepare_component(&wasm.compile_component(&bytes).unwrap(), "run")
.unwrap();
// Default-deny Sandboxed profile…
wasm.spawn_component(&prepared).join().await;
// …or grant capabilities explicitly (here: an 8 MiB heap cap):
let caps = Capabilities::nothing().max_memory(8 << 20);
wasm.spawn_component_with(&prepared, caps).join().await;
}A trap (or a denied capability the guest turns into a trap) exits the process Crashed, so links and supervisors react exactly as for a native process. The runnable host_components example (make example EX=host_components) shows this end to end, including a memory-cap denial.
Core modules. A
wasm32-wasip1core module works the same way withcompile/prepare(module, "run")/spawn(see the wasip1 bridge).