gRPC is an open-source remote procedure call system developed by Google. gRPC allows the system to communicate in and out of data centers, efficiently transferring data from mobile, IoT devices, and backends to one and other.
gRPC comes with pluggable support for load balancing, authentication, tracing, etc., supports bidirectional streaming over HTTP/2, and provides an idiomatic implementation in 10 languages.
Furthermore, gRPC can generate efficient client libraries and uses the protocol buffer format to transfer data over the wire. Protocol buffers are a binary format for data transmission. Since they are binary, protocol buffers can be serialized fast. The structure of each message must be predefined.
The Replay is a weekly newsletter for dev and engineering leaders.
Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.
The Rust community has developed many gRPC implementations, notably the tonic and grpc crates. Both provide a full implementation of gRPC protocols.
tonic?tonic is a fast production-ready gRPC library with async/await support out of the box. It focuses on flexibility and reliability. tonic has full implementation of gRPC protocols over HTTP/2. tonic has built-in support for compiling protocol buffer to Rustlang. It also supports unidirectional as well as bidirectional streaming.
grpc?grpc is not production-ready but is worth keeping an eye on. The crate has a working gRPC protocol implementation and supports TLS.
To show tonic and grpc in action, let’s walk through creating a demo gRPC app.
tonicWe’ll start by creating a Rust project using cargo new grpc-demo-tonic. Create src/server.rs and src/client.rs to hold code for the gRPC server and client, respectively. We’ll add a build.rs to compile protocol buffers.
Some basic boilerplate, such as build.rs, is necessary to compile protocol buffer to Rust code. Some changes to Cargo.toml and file structure are also required.
Cargo.toml:
[package] name = "grpc-demo-tonic" version = "0.1.0" authors = ["anshul <[email protected]>"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] # server binary [[bin]] name = "server" path = "src/server.rs" # client binary [[bin]] name = "client" path = "src/client.rs"
File structure
├── build.rs
├── Cargo.lock
├── Cargo.toml
├── src
├── client.rs
└── server.rs
Start by creating a protocol buffer file.
// version of protocol buffer used
syntax = "proto3";
// package name for the buffer will be used later
package hello;
// service which can be executed
service Say {
// function which can be called
rpc Send (SayRequest) returns (SayResponse);
}
// argument
message SayRequest {
// data type and position of data
string name = 1;
}
// return value
message SayResponse {
// data type and position of data
string message = 1;
}
We can include generated rust code in-app using `tonic`. Let’s create an `hello.rs` file to reuse in both server and client.
// this would include code generated for package hello from .proto file
tonic::include_proto!("hello");
tonicCreate a service by implementing the Say trait for a struct. The service may include multiple RPCs. Since Rust doesn’t support async traits, we have to use an asyc_trait macro to overcome this limitation.
Add the following code to server.rs.
use tonic::{transport::Server, Request, Response, Status};
use hello::say_server::{Say, SayServer};
use hello::{SayResponse, SayRequest};
mod hello;
// defining a struct for our service
#[derive(Default)]
pub struct MySay {}
// implementing rpc for service defined in .proto
#[tonic::async_trait]
impl Say for MySay {
// our rpc impelemented as function
async fn send(&self,request:Request<SayRequest>)->Result<Response<SayResponse>,Status>{
// returning a response as SayResponse message as defined in .proto
Ok(Response::new(SayResponse{
// reading data from request which is awrapper around our SayRequest message defined in .proto
message:format!("hello {}",request.get_ref().name),
}))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// defining address for our service
let addr = "[::1]:50051".parse().unwrap();
// creating a service
let say = MySay::default();
println!("Server listening on {}", addr);
// adding our service to our server.
Server::builder()
.add_service(SayServer::new(say))
.serve(addr)
.await?;
Ok(())
}
The above example uses tokio for async runtime and executor. The MySay struct implements the service Say. The Server type provided by tonic takes services and creates an HTTP server supporting the gRPC protocol on the given address.
tonicSince gRPC defines request and response in a machine-readable format, we don’t need to implement client-side code. The code generated by the protocol buffer compiler already includes client code you can use by directly importing it.
use hello::say_client::SayClient;
use hello::SayRequest;
mod hello;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// creating a channel ie connection to server
let channel = tonic::transport::Channel::from_static("http://[::1]:50051")
.connect()
.await?;
// creating gRPC client from channel
let mut client = SayClient::new(channel);
// creating a new Request
let request = tonic::Request::new(
SayRequest {
name:String::from("anshul")
},
);
// sending request and waiting for response
let response = client.send(request).await?.into_inner();
println!("RESPONSE={:?}", response);
Ok(())
}

To test our small application, run cargo run --bin server and then cargo run --bin client.
grpcFirst, create a Rust project using cargo new grpc-demo-grpc. We need to add two binaries: server and client, just like in the tonic demo. We must also add the proto/hello.proto file and build.rs
Cargo.toml:
[package] name = "grpc-demo-grpc" version = "0.1.0" authors = ["anshul <[email protected]>"] edition = "2018" [[bin]] name="server" path="./src/server.rs" [[bin]] name="client" path="./src/client.rs" [dependencies] protobuf = "2" httpbis = { git = "https://github.com/stepancheg/rust-http2" } grpc ="*" grpc-protobuf="*" [build-dependencies] protoc-rust-grpc = "0.8.2"
build.rs:
protoc_rust_grpc requires protoc compiler in the PATH variables. It cab be downloaded from official website.
fn main() {
// compile protocol buffer using protoc
protoc_rust_grpc::Codegen::new()
.out_dir("src")
.input("./proto/hello.proto")
.rust_protobuf(true)
.run()
.expect("error compiling protocol buffer");
}
grpcThe grpc crate’s APIs are very similar to tonic crate’s. Like tonic, grpc generates code for gRPC communication.
For creating service, the Say trait is implemented on a struct. The implemented struct is passed to the add_server method on the ServerBuilder struct provided by the grpc crate.
use grpc::{ServerHandlerContext,ServerRequestSingle,ServerResponseUnarySink};
// importing generated gRPC code
use hello_grpc::*;
// importing types for messages
use hello::*;
mod hello;
mod hello_grpc;
struct MySay;
impl Say for MySay {
// rpc for service
fn send(
&self,
_: ServerHandlerContext,
req: ServerRequestSingle<SayRequest>,
resp: ServerResponseUnarySink<SayResponse>,
) -> grpc::Result<()> {
// create Response
let mut r = SayResponse::new();
let name = if req.message.get_name().is_empty() {
"world"
} else {
req.message.get_name()
};
// sent the response
println!("greeting request from {}", name);
r.set_message(format!("Hello {}", name));
resp.finish(r)
}
}
fn main() {
let port =50051;
// creating server
let mut server = grpc::ServerBuilder::new_plain();
// adding port to server for http
server.http.set_port(port);
// adding say service to server
server.add_service(SayServer::new_service_def(MySay));
// running the server
let _server = server.build().expect("server");
println!(
"greeter server started on port {}",
port,
);
// stopping the program from finishing
loop {
std::thread::park();
}
}
grpcMaking an RPC call is as simple as creating a client and sending the data. You simply create a request and send it through the client, then wait for a response.
use std::env;
use std::sync::Arc;
// importing generated gRPC code
use hello_grpc::*;
// importing types for messages
use hello::*;
mod hello;
mod hello_grpc;
use grpc::ClientStub;
use grpc::ClientStubExt;
use futures::executor;
fn main() {
let name = "anshul";
let port =50051;
let client_conf = Default::default();
// create a client
let client=SayClient::new_plain("::1", port, client_conf).unwrap();
// create request
let mut req = SayRequest::new();
req.set_name(name.to_string());
// send the request
let resp = client
.send(grpc::RequestOptions::new(), req)
.join_metadata_result();
// wait for response
println!("{:?}", executor::block_on(resp));
}

Rust has excellent support for gRPC. tonic in particular is a fast, production-ready gRPC implementation.
In this tutorial, we learned how to create a gRPC app using both the tonic and grpc crates. We explored protocol buffer and walked through how to compile it to Rust code.
tonic and grpc both support TLS-based authentication using the nativetls crate. This is just the tip of the iceberg; both tonic and grpc have almost full implementations of error handling, load balancing, and authentication.
Here’s a quick visual comparison:
| Feature | tonic |
grpc |
| Production-ready | Yes | No |
| Full async/await support | Yes | No |
| Request and response streaming | Yes | Yes |
| TLS-based authentication | Yes | Yes |
Debugging Rust applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking the performance of your Rust apps, automatically surfacing errors, and tracking slow network requests and load time, try LogRocket.
LogRocket lets you replay user sessions, eliminating guesswork around why bugs happen by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings — compatible with all frameworks.
LogRocket's Galileo AI watches sessions for you, instantly identifying and explaining user struggles with automated monitoring of your entire product experience.
Modernize how you debug your Rust apps — start monitoring for free.

Promise.all still relevant in 2025?In 2025, async JavaScript looks very different. With tools like Promise.any, Promise.allSettled, and Array.fromAsync, many developers wonder if Promise.all is still worth it. The short answer is yes — but only if you know when and why to use it.

Discover what’s new in The Replay, LogRocket’s newsletter for dev and engineering leaders, in the October 29th issue.

Learn about the new features in the Next.js 16 release: why they matter, how they impact your workflow, and how to start using them.

Test out Meta’s AI model, Llama, on a real CRUD frontend projects, compare it with competing models, and walk through the setup process.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now
5 Replies to "Rust and gRPC: A complete guide"
This article could benefit from a bit of proof-reading/editing.
“We can include generated rust code in-app using `tonic`.” should not be in a code-block.
tonic and tokio needed to be added to Cargo.toml – and I’m unsure what the “use” clauses are trying to refer to (e.g. use hello::say_server::SayServer;) – but this may also be because I’m fairly new to Rust.
FWIW, I’m finding this helpful for resolving my confusion: https://github.com/hyperium/tonic/blob/master/examples/helloworld-tutorial.md
agreed.
I tried to follow the tonic step as well but where to put the `.proto`, nor how to build is well defined here.
The given link is much clearer thx.
Totally agree! Just go for the examples from the authors of the tonic crate. This is written by someone that isn’t ready to teach.
Hello, same here. I’m still confused about the generated rust code.
Better off just following https://github.com/hyperium/tonic/blob/master/examples/helloworld-tutorial.md because this “guide” is badly formatted.
“Start by creating a protocol buffer file.” What should the file be called? This is far from a “complete guide”.