Skip to content

Tonic

Networking & ConcurrencyNetworking/RPCRust

What it is

Tonic is a native Rust gRPC implementation built on Tokio, Hyper and Tower, with code generation from .proto files and full streaming support.

A build script compiles .proto files into Rust traits and clients. Implement the generated trait for the server; the client is ready to use.

Installation

cargo add tonic prost tokio --features tokio/full

Getting started

The smallest useful thing you can do with it, and what each part means.

Generated server and client
// build.rs
fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::compile_protos("proto/library.proto")?;
    Ok(())
}

// server
#[tonic::async_trait]
impl BookService for MyService {
    async fn get_book(
        &self,
        request: Request<GetBookRequest>,
    ) -> Result<Response<Book>, Status> {
        let id = request.into_inner().id;
        self.store.find(&id).await
            .map(Response::new)
            .ok_or_else(|| Status::not_found(format!("book {id}")))
    }
}

Server::builder()
    .add_service(BookServiceServer::new(MyService::default()))
    .serve("[::1]:50051".parse()?)
    .await?;
Errors are returned as Status with a gRPC code, which the client can match on — far more useful than a stringly-typed failure.

Advanced usage

Where the library earns its place over a simpler alternative.

Server streaming
type BookStream = Pin<Box<dyn Stream<Item = Result<Book, Status>> + Send>>;

async fn list_books(
    &self,
    _request: Request<ListRequest>,
) -> Result<Response<Self::ListBooksStream>, Status> {
    let (tx, rx) = tokio::sync::mpsc::channel(32);
    let store = self.store.clone();

    tokio::spawn(async move {
        for book in store.all().await {
            // Stop work if the client disconnects.
            if tx.send(Ok(book)).await.is_err() { break; }
        }
    });

    Ok(Response::new(Box::pin(ReceiverStream::new(rx))))
}
Checking the send result is what makes this well-behaved: when the client hangs up the channel closes, and the loop stops instead of producing results nobody wants.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Build fails: could not find protoc
tonic-build needs the protobuf compiler installed, or enable a vendored protoc feature.
Client hangs indefinitely
No deadline was set. Apply a timeout on the request or via a Tower layer.

Best practices

  • Return Status with a meaningful code; clients branch on codes, not messages.
  • Bound the channel in streaming handlers so a slow client applies backpressure.
  • Reuse one Channel for the client — it multiplexes over HTTP/2.
  • Reuse Tower layers from the Axum ecosystem for auth, timeouts and tracing.

Background

Why it exists, and what it was reacting to.

Tonic gave Rust a first-class gRPC stack with no C++ dependency, and because it builds on Tower, its middleware is shared with Axum and the rest of the ecosystem.