3 releases (1 stable)

Uses new Rust 2024

3.0.0 Jan 28, 2026
0.6.0 Jan 22, 2026
0.1.0 Jan 6, 2026

#2 in #roam

Download history 24/week @ 2026-01-02 37/week @ 2026-01-09 28/week @ 2026-01-16 67/week @ 2026-01-23 37/week @ 2026-01-30 24/week @ 2026-02-06

160 downloads per month
Used in 10 crates (2 directly)

MIT/Apache

11KB
232 lines

roam

A Rust-native RPC protocol that's going places.

And remember: roam wasn't built in a day.

What is roam?

roam is a Rust-native RPC protocol. Rust is the lowest common denominator — there's no independent schema language. Rust traits are the schema:

#[roam::service]
pub trait Calculator {
    /// Infallible method — just returns a value
    async fn add(&self, a: i32, b: i32) -> i32;

    /// Fallible method — returns Result<T, E>
    async fn divide(&self, a: i32, b: i32) -> Result<i32, MathError>;

    /// Channels: client sends numbers, server returns sum
    async fn sum(&self, numbers: Rx<i32>) -> i64;

    /// Channels: server sends numbers to client
    async fn generate(&self, count: u32, output: Tx<i32>);

    /// Bidirectional channels
    async fn transform(&self, input: Rx<String>, output: Tx<String>);
}

Implementations for other languages (TypeScript, Swift) are generated from Rust definitions using Rust tooling.

Implementing a Service

impl Calculator for MyCalculator {
    async fn add(&self, _cx: &Context, a: i32, b: i32) -> i32 {
        a + b
    }

    async fn divide(&self, _cx: &Context, a: i32, b: i32) -> Result<i32, MathError> {
        if b == 0 {
            Err(MathError::DivisionByZero)
        } else {
            Ok(a / b)
        }
    }

    async fn sum(&self, _cx: &Context, mut numbers: Rx<i32>) -> i64 {
        let mut total: i64 = 0;
        while let Some(n) = numbers.recv().await.ok().flatten() {
            total += n as i64;
        }
        total
    }
    // ...
}

Using a Client

let client = CalculatorClient::new(connection_handle);

// Simple call
let result = client.add(2, 3).await?;

// Fallible call
match client.divide(10, 0).await? {
    Ok(result) => println!("Result: {result}"),
    Err(MathError::DivisionByZero) => println!("Cannot divide by zero"),
}

// With metadata
let result = client.add(2, 3)
    .with_metadata(vec![("request-id".into(), "abc123".into())])
    .await?;

Middleware

Middleware intercepts requests before and after the handler:

struct AuthMiddleware { /* ... */ }

impl Middleware for AuthMiddleware {
    fn pre<'a>(
        &'a self,
        ctx: &'a mut Context,
        _args: SendPeek<'a>,
    ) -> Pin<Box<dyn Future<Output = Result<(), Rejection>> + Send + 'a>> {
        Box::pin(async move {
            let token = ctx.metadata().get("auth-token");
            match validate_token(token) {
                Ok(user) => {
                    ctx.extensions.insert(user);
                    Ok(())
                }
                Err(_) => Err(Rejection::unauthenticated("invalid token")),
            }
        })
    }
}

// Add to dispatcher
let dispatcher = CalculatorDispatcher::new(handler)
    .with_middleware(AuthMiddleware::new());

Observability

Built-in OpenTelemetry integration with distributed tracing:

use roam_telemetry::{TelemetryMiddleware, OtlpExporter, TracingCaller};

// Server side: export spans to Tempo/Jaeger
let exporter = OtlpExporter::new("http://tempo:4318/v1/traces", "my-service");
let dispatcher = CalculatorDispatcher::new(handler)
    .with_middleware(TelemetryMiddleware::new(exporter.clone()));

// Client side: automatic trace propagation
let caller = TracingCaller::new(connection_handle, exporter);
let client = DownstreamClient::new(caller);
// Calls automatically inject traceparent headers

Features

  • Bidirectional RPC — Request/response with correlation
  • ChannelsTx<T>/Rx<T> with credit-based flow control
  • Virtual connections — Multiple independent contexts on a single link
  • Transport-agnostic — TCP, WebSocket, shared memory, with QUIC/WebTransport planned
  • Type-safeFacet-based serialization

Language Support

Language Status
Rust Reference implementation
TypeScript Generated client/server
Swift Generated client/server

Transport Bindings

Transport Framing Status
TCP / Unix sockets 4-byte length prefix
WebSocket Binary frames
Shared Memory Hub Lock-free rings
HTTP Bridge WebSocket upgrade
QUIC / WebTransport Native streams Planned

Project Structure

rust/           # Rust implementation (roam, roam-wire, roam-session, etc.)
typescript/     # TypeScript packages (roam-core, roam-tcp, roam-ws)
swift/          # Swift packages
spec/           # Compliance test suite
docs/           # Specifications

Quick Start

# Run Rust compliance tests
just rust

# Run TypeScript compliance tests  
just ts

# Run all language tests
just all

Specification

Read the spec for the formal protocol definition.

License

MIT OR Apache-2.0

Dependencies

~3MB
~52K SLoC