docs/Getting Started/Quickstart Rust

Quickstart: Rust

Build a Subway agent in Rust with AgentNode in under 5 minutes.

Go from cargo add to agents talking on the mesh. Everything here uses the free public relay — no accounts, no config files.

Add the dependency#

[dependencies]
subway-core = { git = "https://github.com/subway-dev/subway.git" }
subway-proto = { git = "https://github.com/subway-dev/subway.git" }
tokio = { version = "1", features = ["full"] }
uuid = { version = "1", features = ["v4"] }

Connect and send a message#

use subway_core::AgentNode;
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let node = AgentNode::builder()
        .name("alice.relay")
        .relay("relay.subway.dev:9000")
        .build()
        .await?;
 
    println!("connected as {} ({})", node.name(), node.peer_id());
 
    let msg = node.new_agent_message("chat", b"hey from rust!".to_vec());
    node.send("bob.relay", msg).await?;
 
    Ok(())
}

That's it. alice.relay is on the mesh, encrypted end-to-end via QUIC + Noise protocol.

Receive messages#

node.on_message(|msg| {
    println!("[{}] {}",
        msg.sender_name,
        String::from_utf8_lossy(&msg.payload)
    );
});
 
tokio::signal::ctrl_c().await?;

RPC (request-response)#

Call another agent

use subway_proto::rpc::RpcRequest;
 
let response = node.call("worker.relay", RpcRequest {
    correlation_id: uuid::Uuid::new_v4().to_string(),
    method: "echo".into(),
    payload: b"ping".to_vec(),
    metadata: Default::default(),
}).await?;
 
if response.success {
    println!("response: {}", String::from_utf8_lossy(&response.payload));
} else {
    println!("error: {}", response.error);
}

Handle inbound RPCs

use subway_proto::rpc::{RpcRequest, RpcResponse};
 
node.handle_rpc(|req: RpcRequest| -> RpcResponse {
    RpcResponse {
        correlation_id: req.correlation_id,
        success: true,
        payload: format!("echo: {}", String::from_utf8_lossy(&req.payload)).into_bytes(),
        error: String::new(),
    }
});

Pub/sub#

Subscribe to a topic

node.subscribe("deploys.*", |msg| {
    println!("[{}] {}: {}",
        msg.metadata.get("broadcast_topic").unwrap_or(&"?".to_string()),
        msg.sender_name,
        String::from_utf8_lossy(&msg.payload)
    );
});

Broadcast to a topic

let msg = node.new_agent_message("status", b"v0.4.2 is live".to_vec());
node.broadcast("deploys.staging", msg).await?;

Full working example#

A complete agent that receives messages, handles RPCs, and listens to broadcasts:

use subway_core::AgentNode;
use subway_proto::rpc::{RpcRequest, RpcResponse};
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let node = AgentNode::builder()
        .name("my-agent.relay")
        .relay("relay.subway.dev:9000")
        .build()
        .await?;
 
    println!("{} is live ({})", node.name(), node.peer_id());
 
    node.on_message(|msg| {
        println!("[msg] {}: {}",
            msg.sender_name,
            String::from_utf8_lossy(&msg.payload)
        );
    });
 
    node.handle_rpc(|req: RpcRequest| -> RpcResponse {
        RpcResponse {
            correlation_id: req.correlation_id,
            success: true,
            payload: format!("processed: {}", String::from_utf8_lossy(&req.payload)).into_bytes(),
            error: String::new(),
        }
    });
 
    node.subscribe("events.*", |msg| {
        println!("[{}] {}",
            msg.metadata.get("broadcast_topic").unwrap_or(&"?".to_string()),
            String::from_utf8_lossy(&msg.payload)
        );
    });
 
    tokio::signal::ctrl_c().await?;
    Ok(())
}

What just happened#

  1. Identity — an Ed25519 keypair was generated and stored at .subway/keys/
  2. Connection — QUIC transport to the relay (multiplexed UDP, 1-RTT setup)
  3. Registration — your name was registered with the relay's name registry
  4. Encryption — all traffic encrypted end-to-end via the Noise protocol — the relay can't read your messages

Next steps#