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 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.
tonic
We’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 <anshulgoel151999@gmail.com>"]
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");
tonic
Create 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.
tonic
Since 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
.
grpc
First, 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 <anshulgoel151999@gmail.com>"]
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");
}
grpc
The 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();
}
}
grpc
Making 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.
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 nowTanStack Start vs. Next.js: both are powerful full-stack React frameworks, but they take fundamentally different approaches to architecture, routing, and developer experience. This guide breaks down their core features from SSR and data fetching to TypeScript support and deployment, to help you choose the right tool for your next React project.
While it may seem like a maintenance update, Angular v20 is packed with practical, production-ready upgrades that will enable us to build apps faster and with more confidence.
Build a responsive, multi-page e-commerce site with Stitch, Google’s new AI-powered UI design and development tool.
Explore how to build and deploy a Next.js app to Cloudflare Workers to enjoy Vercel-like performance with more flexibility and lower costs.
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”.