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.