Demo code grpc in Rust
Find a file
rust-grpc-demo 9231389dae Threading model docs + channel-scaling experiment
- README: clarify tonic runs tokio's multi-threaded work-stealing runtime
  (like Go's GOMAXPROCS), neither is thread-per-core
- channeltest: spread load over N tonic channels — 1 conn 42.5k req/s,
  8 conns 133.5k req/s; single-channel plateau is per-connection
  serialization, not CPU
- benchmark: connection-column framing (single channel by construction)
2026-09-08 19:37:58 +02:00
proto/greet/v1 gRPC demo in Rust: tonic + prost, all 4 RPC modes, benchmark 2026-09-08 17:33:16 +02:00
src Threading model docs + channel-scaling experiment 2026-09-08 19:37:58 +02:00
.gitignore gRPC demo in Rust: tonic + prost, all 4 RPC modes, benchmark 2026-09-08 17:33:16 +02:00
build.rs gRPC demo in Rust: tonic + prost, all 4 RPC modes, benchmark 2026-09-08 17:33:16 +02:00
Cargo.toml gRPC demo in Rust: tonic + prost, all 4 RPC modes, benchmark 2026-09-08 17:33:16 +02:00
README.md Threading model docs + channel-scaling experiment 2026-09-08 19:37:58 +02:00

rust-grpc-demo

A minimal demo of a Rust server and client communicating over protobuf using gRPC via tonic (the successor of github.com/grpc/grpc-rust) and prost, with codegen by tonic-prost-build + protox (no system protoc needed).

This is the Rust counterpart of the sibling repos, all sharing the same schema, all four RPC modes, and the same benchmark methodology:

  • pyrpc-demo — Connect RPC + protobuf-py (Python)
  • pygrpc-demo — gRPC + classic protobuf (Python)
  • this repo — gRPC + tonic/prost (Rust)
  • golang-grpc-demo — gRPC + protobuf-go (Go)

Project layout

proto/greet/v1/greet.proto   # the schema: greet.v1.GreetService (4 RPC modes)
build.rs                     # codegen: protox parses, tonic_prost_build emits
src/server.rs                # gRPC server (tonic + tokio)
src/client.rs                # gRPC client (tonic)
src/benchmark.rs             # performance benchmark (codec + RPC throughput)

Dependencies (Cargo.toml)

Dependency Why it's here
tonic gRPC runtime for Rust (HTTP/2 via hyper, tokio-based).
tonic-prost Codec connecting tonic to prost messages (used by generated code).
prost Protobuf message derive and wire format.
tokio Async runtime for server/client/benchmark.
tokio-stream Stream adapters for streaming RPCs.
futures Stream combinators (client demos).
tonic-prost-build (build) Generates service stubs from the .proto.
protox (build) Pure-Rust protoc replacement — parses .proto to a FileDescriptorSet so no system protoc is required.

Build & run

cargo build --release
cargo run --release --bin server    # terminal 1
cargo run --release --bin client    # terminal 2

Expected client output (all four RPC kinds):

--- unary: Greet ---
[client] Hello, world!

--- server streaming: GreetStream ---
[client] Hello #1, streamer!
[client] Hello #2, streamer!
[client] Hello #3, streamer!

--- client streaming: GreetCount ---
[client] server counted 3 requests

--- bidirectional streaming: GreetChat ---
[client] Hi, alice!
[client] Hi, bob!
[client] Hi, carol!

Threading model

Both this repo and the Go sibling are multi-threaded out of the box — neither is thread-per-core:

  • tonic (this repo) runs on tokio's multi-threaded work-stealing scheduler (rt-multi-thread; #[tokio::main] defaults to it, workers = available_parallelism(), 10 on the benchmark machine). Requests are futures, not threads.
  • grpc-go schedules goroutines across GOMAXPROCS OS threads (defaults to core count).

So why does the single-channel benchmark plateau at ~1.9× while Go reaches 3.7× on its single connection? Not CPU — per-connection serialization. src/bin/channeltest.rs spreads the same load (c=64) over N channels (each channel = one HTTP/2 connection):

channels req/s
1 42,548
2 74,017
4 109,840
8 133,522

With 8 channels Rust is the fastest stack in the whole comparison (~134k req/s). Run it yourself:

cargo run --release --bin server       # terminal 1
cargo run --release --bin channeltest  # terminal 2

Benchmark

Same methodology as the sibling repos:

  1. Codec — in-process serialize/deserialize of GreetRequest at 64 / 128 / 256 / 512 / 1024 / 2048 KiB payloads, each timed ≥0.5 s per direction.
  2. Unary throughput vs concurrency — 2,000 Greet round-trips at each concurrency level (1, 4, 16, 64), with live TCP connection counts (via netstat).
  3. Unary throughput, large payloads — 200 round-trips, 2 MiB payloads.
  4. Bidi streaming throughput — 5,000 messages over one GreetChat stream.
cargo run --release --bin server       # terminal 1
cargo run --release --bin benchmark    # terminal 2

See the cross-language results table in pyrpc-demo's README.