12 Rust Guide
spotdemo4 edited this page 2026-07-11 11:35:52 -04:00

Rust Guide

The Rust runtime lives in trevrpc-rust/ and is published as the trevrpc crate in this repository. It provides the client and server runtime, Quinn, HTTP/3, and WebTransport transports, shared wire types, status handling, metadata validation, and stream helpers.

Features

Default features enable client, server, quinn, http3, and webtransport.

Feature Purpose
client Client helper APIs and transport trait
server Server routing, authorization, metrics, and options
quinn Native QUIC transport and serving through Quinn
http3 Ordinary HTTP/3 serving through h3 and h3-quinn
webtransport Unified h3-webtransport server and web-transport-quinn native client paths
tracing Structured RPC lifecycle tracing hooks

The runtime exports trevrpc::ALPN, currently b"trevrpc/1", for QUIC TLS configuration.

Generated Service Shape

For the greeter example, generated Rust code has the same shape as this hand-written example:

#[trevrpc::async_trait]
pub trait Greeter: Send + Sync + 'static {
    async fn say_hello(
        &self,
        request: HelloRequest,
    ) -> Result<HelloReply, trevrpc::Status>;

    async fn lots_of_replies(
        &self,
        request: HelloRequest,
    ) -> Result<trevrpc::BoxMessageStream<HelloReply>, trevrpc::Status>;

    async fn lots_of_greetings(
        &self,
        requests: trevrpc::BoxMessageStream<HelloRequest>,
    ) -> Result<HelloReply, trevrpc::Status>;

    async fn bidi_hello(
        &self,
        requests: trevrpc::BoxMessageStream<HelloRequest>,
    ) -> Result<trevrpc::BoxMessageStream<HelloReply>, trevrpc::Status>;
}

Implement a Service

struct GreeterService;

#[trevrpc::async_trait]
impl greeter::Greeter for GreeterService {
    async fn say_hello(
        &self,
        request: greeter::HelloRequest,
    ) -> Result<greeter::HelloReply, trevrpc::Status> {
        Ok(greeter::HelloReply {
            message: format!("hello, {}", request.name),
        })
    }

    async fn lots_of_replies(
        &self,
        request: greeter::HelloRequest,
    ) -> Result<trevrpc::BoxMessageStream<greeter::HelloReply>, trevrpc::Status> {
        Ok(trevrpc::stream::from_iter([
            greeter::HelloReply { message: format!("hello, {}", request.name) },
            greeter::HelloReply { message: format!("goodbye, {}", request.name) },
        ]))
    }
}

Register a Server

Generated registration functions add routes to trevrpc::server::Server:

let mut server = trevrpc::server::Server::new();

server.set_options(
    trevrpc::server::ServerOptions::new()
        .with_max_concurrent_connections(Some(512))
        .with_max_concurrent_streams_per_connection(Some(64))
        .with_max_concurrent_requests(Some(1024)),
);

server.set_authorizer(trevrpc::server::MetadataValueAuthorizer::bearer(
    "trevrpc-example-token",
));

greeter::register_greeter(&mut server, GreeterService);

The Quinn server API accepts a caller-created quinn::Endpoint:

server
    .serve_quinn_with_shutdown(endpoint, async {
        let _ = tokio::signal::ctrl_c().await;
    })
    .await?;

TLS, certificates, client identity, and QUIC transport configuration remain the application's responsibility. Set server_crypto.alpn_protocols = vec![trevrpc::ALPN.to_vec()] before creating the endpoint. When applying TrevRPC's derived Quinn limits, call trevrpc::quinn::configure_server_config with trevrpc::quinn::TransportMode::Native; use TransportMode::WebTransport for an endpoint that serves WebTransport so the required peer-initiated HTTP/3 unidirectional streams remain available.

For a combined native QUIC, HTTP/3, and WebTransport server, enable HTTP/3 in ServerOptions, advertise both trevrpc::ALPN and trevrpc::HTTP3_ALPN, and call serve_quinn_and_webtransport or serve_quinn_and_webtransport_with_shutdown. See HTTP/3 and WebTransport.

Build a Client

Generated clients use the long-lived application channel:

let channel = trevrpc::client::Channel::connect(
    endpoint.clone(),
    addr,
    "localhost",
).await?;
let client = greeter::GreeterClient::new(channel.clone());

let reply = client
    .say_hello(
        greeter::HelloRequest { name: "TrevRPC".into() },
        trevrpc::client::CallOptions::new()
            .with_timeout(std::time::Duration::from_secs(5))
            .with_metadata("authorization", b"Bearer trevrpc-example-token".to_vec()),
    )
    .await?;

Channel reuses the Quinn endpoint and reconnects in the background for future calls. Each RPC snapshots one ready generation. In-flight calls fail if that generation is lost; TrevRPC never retries, replays, resumes, or moves them. New calls fail immediately with Unavailable while reconnecting. Call channel.wait_until_ready().await? before issuing work when waiting is intentional.

Routine constructors use fixed reconnect defaults: 100 ms initial delay, multiplier 2, 20 percent jitter, and a 30 second cap. They do not expose reconnect-policy tuning.

For advanced integrations, benchmarks, diagnostics, or deterministic tests that explicitly own one established connection, use the raw transport:

let connection = endpoint.connect(addr, "localhost")?.await?;
let transport = trevrpc::advanced::RawQuinnTransport::new(connection);
let client = greeter::GreeterClient::new(transport);

Client-streaming and bidirectional-streaming client methods return call objects:

let mut greetings = client.lots_of_greetings(call_options()).await?;
greetings.send(greeter::HelloRequest { name: "first".into() }).await?;
greetings.send(greeter::HelloRequest { name: "second".into() }).await?;
let summary = greetings.close_and_recv().await?;

let mut bidi = client.bidi_hello(call_options()).await?;
bidi.send(greeter::HelloRequest { name: "first".into() }).await?;
let reply = bidi.recv().await?.expect("reply");
bidi.close_send().await?;

Generated clients also expose _from_stream variants for fixed or pre-existing request streams. These avoid the interactive send-channel path and are preferable when the full request stream is already available:

let summary = client
    .lots_of_greetings_from_stream(
        trevrpc::stream::from_iter([
            greeter::HelloRequest { name: "first".into() },
            greeter::HelloRequest { name: "second".into() },
        ]),
        call_options(),
    )
    .await?;

At application shutdown, call channel.close() to stop reconnecting and then endpoint.wait_idle().await if the application owns and wants to drain that endpoint.

Set client_crypto.alpn_protocols = vec![trevrpc::ALPN.to_vec()] on the client TLS config. Pass trevrpc::quinn::TransportMode::Native to configure_client_config for native TrevRPC, or TransportMode::WebTransport when configuring Quinn for WebTransport.

For routine WebTransport clients, create a web_transport_quinn::Client and pass it with an HTTPS origin to trevrpc::client::Channel::connect_webtransport. The routine constructor always uses /trevrpc:

let webtransport_client = web_transport_quinn::ClientBuilder::new()
    .with_server_certificates(vec![cert_der])?;
let channel = trevrpc::client::Channel::connect_webtransport(
    webtransport_client,
    "https://127.0.0.1:5000",
).await?;
let client = greeter::GreeterClient::new(channel.clone());

Advanced APIs live under trevrpc::advanced: RawQuinnTransport and RawWebTransport own one connection or session; ChannelOperations exposes Quinn endpoint addresses and rebinding; ChannelConfigOperations exposes reconnect tuning; and connect_webtransport_channel_with_request accepts a custom CONNECT request. Raw transports do not reconnect and are not the routine application API.

Where TrevRPC controls QUIC setup, 0-RTT application data is disabled. TLS session resumption may shorten a later handshake but never changes generation pinning or RPC failure semantics.

Client Options

trevrpc::client::CallOptions controls per-call behavior.

Option Default
Timeout None
Max unary response body 4 MiB
Max response messages 4096
Max cumulative response stream body 64 MiB
Stream idle timeout 30 seconds
Metadata Empty

Use with_metadata for request metadata. Keys are normalized to lowercase and validated before sending.

Server Options

trevrpc::server::ServerOptions controls server limits.

Option Default
Max frame size 4 MiB
Max concurrent connections 256
Max concurrent streams per connection 64
Max concurrent requests 1024
Graceful shutdown timeout 30 seconds
Initial request timeout 10 seconds
Max stream messages 4096
Max cumulative stream body 16 MiB
Stream idle timeout 30 seconds
Ordinary HTTP/3 Disabled
HTTP/3 path /trevrpc

Set a limit to None to disable that specific limit where the API accepts Option<usize> or Option<Duration>.

Streams

Rust streams implement:

#[trevrpc::async_trait]
pub trait MessageStream<T>: Send {
    async fn next(&mut self) -> Option<trevrpc::Result<T>>;
}

Helpful constructors and adapters live under trevrpc::stream:

  • empty<T>() creates an empty stream.
  • from_iter([...]) creates a stream from in-memory values.
  • encode(stream) converts protobuf messages into byte bodies.
  • decode::<T>(stream) converts byte bodies into protobuf messages.

Statuses and Errors

Rust uses trevrpc::Status with canonical status codes matching gRPC numeric values. Service implementations can return Status::invalid_argument, Status::deadline_exceeded, Status::unauthenticated, Status::unimplemented, and other constructors.

Runtime operations return trevrpc::Result<T>, which wraps protocol errors, frame-size errors, protobuf decode errors, and statuses.

Observability

Server metrics use the trevrpc::server::Metrics trait:

pub trait Metrics: Send + Sync + 'static {
    fn rpc_started(&self, event: &RpcStarted) {}
    fn rpc_finished(&self, event: &RpcFinished) {}
}

Metrics callbacks run inline on the RPC task and must be fast. Forward to a channel or recorder instead of doing blocking I/O.

Enable the optional tracing Cargo feature for structured RPC lifecycle and Quinn frame events. Frame events use the trevrpc::quinn::frames target and are controlled by the application's tracing subscriber and filter; the former Rust frame-trace environment switch is no longer supported.