Write a Rust component
You write Rust. RUSM compiles it to wasm32-wasip2 and runs it as a sandboxed, supervised process — isolated memory, capability-gated I/O, crash-recovered by the supervisor. No cargo-component, no jco, no manual WIT plumbing. Write logic; RUSM handles the rest.
Scaffold & run in 30 seconds
rusm new creates a complete new RUSM project — rusm.toml, a components/ folder, and a starter component ready to build and serve:
rusm new myapp --rust # new project with a Rust HTTP component
cd myapp
rusm build # cargo build --target wasm32-wasip2 → wasm/api.wasm
rusm serve # live on http://127.0.0.1:8080Want WebSocket or SSE instead?
rusm new myapp --rust --protocol ws # new project with a WebSocket component
rusm new myapp --rust --protocol sse # new project with an SSE componentAdding a component to an existing project
Use rusm generate component <name> [--lang ts|rust|go] [--protocol http|ws|sse] — it adds a new components/<name>/ and the matching rusm.toml entry without touching anything else. rusm generate bridge <name> scaffolds a new host bridge the same way.
A component is a folder under components/ with its own Cargo.toml and src/lib.rs:
my-app/
├── rusm.toml
├── components/
│ └── api/
│ ├── Cargo.toml # rusm-rs dep, crate-type = ["cdylib"]
│ └── src/lib.rs
└── wasm/ # rusm build writes api.wasm hereTwo shapes
Service — a module of functions
Annotate a module with #[rusm_rs::service]. RUSM generates the receive → dispatch → reply loop and a typed Client — so callers reach it with ordinary method calls that are actually cross-process messages:
// components/calc/src/lib.rs
#[rusm_rs::service]
pub mod calc {
pub fn add(a: i64, b: i64) -> i64 { a + b }
pub fn count_to(n: i64) -> impl Iterator<Item = i64> { 1..=n } // streaming
pub fn work(progress: rusm_rs::Callback<i64>) -> String { // callback
for pct in [25, 50, 100] { progress.call(pct); }
"done".into()
}
}Worker — a #[rusm_rs::main] fn
A one-shot entry point: runs once, does the job, exits. Use calc::Client::spawn to reach the service — the typed client hides spawn + send + receive behind a plain method call:
// components/commander/src/lib.rs
use calc::calc::Client as CalcClient;
#[rusm_rs::main]
fn run() {
let calc = CalcClient::spawn("calc").unwrap();
println!("2 + 3 = {}", calc.add(2, 3).unwrap()); // typed call
for n in calc.count_to(3).unwrap() { println!("{n}"); } // streaming
calc.work(|pct| println!("progress {pct}")).unwrap(); // callback
}The calc crate is a path dependency — the Client type lives there (generated by the macro), and commander imports it. Neither component shares runtime memory; they share only the type.
Declare in rusm.toml
[components.calc]
capability = "sandboxed"
[components.commander]
capability = "trusted" # inherits allow-spawnBuild & run
rusm build # cargo build --target wasm32-wasip2 per component → wasm/*.wasm
rusm run # spawn them per rusm.toml
rusm dev # build + run, then watch ./components and hot-reload on every saveOne toolchain, no extra steps — cargo build --target wasm32-wasip2 componentizes directly. rusm dev keeps running: save a file and the component rebuilds and reloads automatically.
What rusm-rs gives you
The full actor toolkit, all typed and serde-backed:
rusm_rs::me() | this process's Pid |
send_bytes(pid, &[u8]) / send(pid, &T) | send a message |
receive_bytes() / receive::<T>() | wait for a message (parks the fiber) |
spawn("name") | spawn a component by rusm.toml name |
svc::Client::connect(pid) | typed cross-process call (the #[service]-generated client) |
register("name") / whereis("name") | named registry |
register_tag("tag") / whereis_tag("tag") | process-group tags |
send_after(pid, ms, msg) / cancel_timer(h) | timers |
monitor(pid) | watch for a process exit (a __down message) |
kill(pid) | terminate another process |
Stream::open(pid) / Stream::accept() | byte streams |
set_label("label") | a label for the observer |
Errors are ordinary Results. Logging is the standard log crate — log::info!, log::error! — routed to the node's unified stream by #[rusm_rs::main] / #[handlers] automatically. The host stamps the time, component#pid, and severity. No setup, no allow-stdio.
Same wire as TypeScript and Go
A Rust service and a TypeScript or Go caller interoperate out of the box — they speak the same JSON wire. Mix languages freely; the type system keeps each component honest within its own boundary.
Go deeper
- Call another component —
Client::spawn,connectto a resident, call with a deadline - Serve HTTP / WS / SSE —
#[rusm_rs::handlers]for routed HTTP,ws::serve,sse::serve - Coordinate & supervise — links, monitors,
#[rusm_rs::supervisor] - Runnable todo-board — service + worker + streaming + callback, end to end