rgoussu@goussu: ~/library/rust/protocols
~/library/rust/protocols cat grpc.md

gRPC in Rust — tonic & prost

# tonic as the de-facto gRPC stack — prost codegen from .proto, tonic-build and buf, all four streaming modes on Tokio, tower middleware sharing, and the operational endpoints.

Conceptsaved 2026-08-09 #rust#protocols#grpc#tonic#protobuf

Overview

gRPC is arguably Rust's strongest protocol suit: tonic (server + client over hyper/h2) with prost (protobuf codegen) is a first-class stack, and — the differentiating fact — a genuine tower citizen: a tonic server is a tower Service, so the auth/tracing/limit middleware you wrote for axum applies to your gRPC endpoints unchanged. Where grpc-java is the reference implementation and grpc-go the flagship, tonic is a community stack that became the obvious default — including inside major infrastructure (linkerd's proxy among others).

Key points

  • Codegen at build time: tonic-build in build.rs (or tonic_build via buf) compiles .proto into prost structs (plain Rust with #[derive(Message)]) and tonic service traits — no reflection, no runtime descriptors unless you opt in.
  • Implementing a service = implementing a trait: #[tonic::async_trait] impl Greeter for MyGreeter with Request<T>/Response<T> — the schema-first contract as a compiler obligation, the strongest form of the pattern across the three ecosystems.
  • All four streaming modes map to Streams: unary, server-streaming (type ListStream = Pin<Box<dyn Stream<...>>>, or tokio_stream wrappers), client-streaming (Streaming<T> argument), bidirectional. Backpressure comes from h2 flow control + the async pull model, no extra API.
  • tower is the middleware story: interceptors for the light auth/metadata cases, full tower Layers for everything else — plus tonic-specific layers from the ecosystem (tonic-tracing, tonic middleware for metrics).
  • The operational endpoints ship as crates: tonic-health (grpc.health.v1 for load balancers), tonic-reflection (server reflection for grpcurl/Postman), tonic-web (gRPC-Web for browsers, as a layer).
  • buf over raw protoc: the same schema-governance conclusions as the neighbours — buf generate with the prost/tonic plugins, buf breaking in CI; proto files are the shared polyglot contract, Rust is just another consumer.
  • prost design choices to know: optional maps to Option<T>, unknown enum values decode to a sentinel (i32 repr), bytes can map to Bytes for zero-copy; well-known types via prost-types. No runtime reflection means grpcurl-style dynamic clients need the reflection service turned on.

Examples

// build.rs
fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::configure()
        .bytes(["."])
        .compile_protos(&["proto/orders.proto"], &["proto"])?;
    Ok(())
}
#[tonic::async_trait]
impl Orders for OrderSvc {
    async fn get_order(&self, req: Request<GetOrderRequest>)
        -> Result<Response<Order>, Status> {
        let order = self.store.find(req.into_inner().id).await
            .map_err(|_| Status::not_found("no such order"))?;
        Ok(Response::new(order))
    }
}

Server::builder()
    .layer(TraceLayer::new_for_grpc())         // the same tower-http layer family
    .add_service(OrdersServer::new(svc))
    .add_service(health_service)
    .serve(addr).await?;

Related