On this page
tonic (gRPC)
gRPC uses protobuf to define strongly-typed service interfaces—changing a .proto file requires all callers to update, otherwise compilation fails (a compiler-verified contract, not a convention). tonic is Rust’s gRPC implementation; codegen generates Rust traits, streaming types (unary/client/server/bidi), and an interceptor layer from .proto files, each corresponding to different usage scenarios and cross-cutting logic.
Differences Between gRPC and REST
REST expresses operations via HTTP methods and URLs, with payloads typically in JSON. gRPC uses protobuf to define strongly-typed service interfaces, with payloads encoded as binary protobuf messages. The difference is not just format—it’s the programming model. REST interface definitions are "conventions" (API docs + boilerplate). gRPC interface definitions are compiler-verified contracts—changing a .proto file requires all callers to update, otherwise compilation fails.
tonic is the most mature gRPC implementation in the Rust ecosystem, built on tokio + tower + prost (the protobuf compiler).
From .proto to Rust Types
syntax = "proto3";
package greeter;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply); // unary: 1 request → 1 response
rpc StreamHello (HelloRequest) returns (stream HelloReply); // server streaming
rpc Chat (stream ChatMessage) returns (stream ChatMessage); // bidi streaming
}
message HelloRequest { string name = 1; }
message HelloReply { string message = 1; }
In build.rs, the tonic_build::compile_protos() call invokes protoc + prost to generate:
GreeterServer<T: Greeter>— server wrapper (wraps theGreetertrait as a towerService)Greetertrait — interface for business logic (methods you implement)GreeterClient— client stub (async methods for callers to use)
The generated code implements trait methods using #[tonic::async_trait]—this allows traits to contain async fn (native Rust traits do not yet support async fn).
Server: Implementing the Trait
;
builder
.add_service
.serve.await?;
Status is gRPC’s standard error type—not an HTTP status code. gRPC defines 17 status codes: Ok, Cancelled, InvalidArgument, NotFound, AlreadyExists, PermissionDenied, Unauthenticated, ResourceExhausted, Unimplemented, Internal, Unavailable, DeadlineExceeded. Each code has a clear semantic meaning—unlike HTTP, where 400 might mean "malformed format" or "missing parameter."
Streaming: More Than "One Request, One Response"
// Server streaming: server sends data incrementally
async
// Bidirectional streaming: both sides send simultaneously
async
Key point about streaming: server streaming = server returns stream T, and the client receives items one by one. Bidirectional = both sides send streams independently. This differs from WebSocket’s "bidirectional channel"—in gRPC streaming, each message is still an independent, strongly-typed protobuf message.
Interceptors: Request-Level Middleware
builder
.add_service
Interceptors execute before handlers—enabling authentication, rate limiting, metadata injection, etc. The difference from tower Layer: interceptors are a tonic-specific concept, while Layer is tower’s generic abstraction. Under the hood, tonic interceptors are essentially thin wrappers around tower Service.
References
- tonic: github.com/hyperium/tonic
- gRPC: grpc.io (spec, status codes)
- protobuf: protobuf.dev (encoding, proto3 language guide)
Keywords: tonic, gRPC, protobuf, streaming, interceptor, Status, unary, bidirectional