Multi-Service
A single-process Rust example showing the #[command_service] macro, cross-registry routing, and event fan-out — the Rust equivalent of the TypeScript Web Workers example, minus the worker boundary.
Source: rust/examples/multi-service/.
Topology
Section titled “Topology” ┌───────────┐ InMemoryChannel ┌─────────────┐ │ root │ ◄─────────────────────► │ worker │ │ │ │ │ │ GreetSvc │ │ MathSvc │ └───────────┘ └─────────────┘- root — hosts
GreetService(greet.hello,greet.farewell) - worker — hosts
MathService(math.add,math.sub,math.mul). Configured withrouter_channel = "root", so its commands appear onrootas remote.
The REPL runs against root. Calls to greet.* execute locally; math.* route across the channel to worker.
Run it
Section titled “Run it”make rs-start-example multi-service# orcd rust && cargo run -p multi-serviceWhat the code shows
Section titled “What the code shows”-
Macro-based command registration —
MathServiceandGreetServiceeach use#[command_service]on animplblock, with#[command("id", description = "...")]per method. One call toService.register(®istry).await?registers everything. -
InMemoryChannel::pair— both halves of the transport created in one line, no OS sockets or worker threads. -
Runtime-agnostic driver spawning —
register_channelreturns a future the caller spawns onfutures::executor::ThreadPool. Swap in tokio or async-std with no library changes. -
Cross-registry routing — calling
math.addonrootescalates via the channel toworker, executes, and the response travels back. Same code path the TS library uses for arouterChannelsetup. -
Interactive REPL —
listshows commands with their advertised JSON Schema;callwalks the schema and prompts for each field;emitbroadcasts events and shows them arriving onworker.
The service struct
Section titled “The service struct”use coralstack_cmd_ipc::prelude::*;
#[payload]pub struct BinaryOpReq { pub a: i64, pub b: i64 }
pub struct MathService;
#[command_service]impl MathService { #[command("math.add", description = "Add two integers")] async fn add(&self, req: BinaryOpReq) -> Result<i64, CommandError> { Ok(req.a + req.b) }
#[command("math.sub", description = "Subtract b from a")] async fn sub(&self, req: BinaryOpReq) -> Result<i64, CommandError> { Ok(req.a - req.b) }
#[command("math.mul", description = "Multiply two integers")] async fn mul(&self, req: BinaryOpReq) -> Result<i64, CommandError> { Ok(req.a * req.b) }}Wiring the two registries
Section titled “Wiring the two registries”let (ch_for_root, ch_for_worker) = InMemoryChannel::pair("worker", "root");
let root = CommandRegistry::new(Config { id: Some("root".into()), ..Default::default()});let worker = CommandRegistry::new(Config { id: Some("worker".into()), router_channel: Some("root".into()), ..Default::default()});
let driver_root = root.register_channel(ch_for_root).await?;let driver_worker = worker.register_channel(ch_for_worker).await?;
pool.spawn(driver_root)?;pool.spawn(driver_worker)?;
GreetService.register(&root).await?;MathService.register(&worker).await?;Sample session
Section titled “Sample session”› list+----------------+-----------------------+| ID | Description |+----------------+-----------------------+| greet.hello | Greet someone by name || greet.farewell | Say goodbye || math.add | Add two integers || math.sub | Subtract b from a || math.mul | Multiply two integers |+----------------+-----------------------+
› callpick (number or id): 3 a (integer, required): 7 b (integer, required): 11[cli] math.add -> ok (0.2ms)18
› emit user.updated {"id":42,"name":"Ada"}[worker] event received: user.updated {"id":42,"name":"Ada"}