1. Introduction
In this codelab, you'll use gRPC-Rust to create a client and server that form the foundation of a route-mapping application written in Rust.
By the end of the tutorial, you will have a client that connects to a remote server using the official Rust implementation of the gRPC protocol to fetch the name or postal address of a location at specific coordinates on a map. A fully fledged application might use this client-server design to enumerate or summarize points of interest along a route.
The service is defined in a Protocol Buffers file, which will be used to generate boilerplate code for the client and server so that they can communicate with each other, saving you time and effort in implementing that functionality.
This generated code takes care of not only the complexities of the communication between the server and client, but also data serialization and deserialization.
What you'll learn
- How to use Protocol Buffers to define a service API.
- How to build a gRPC-based client from a Protocol Buffer definition using automated code generation.
- An understanding of client-server communication with gRPC.
This codelab is aimed at Rust developers new to gRPC or seeking a refresher of gRPC, or anyone else interested in building distributed systems. No prior gRPC experience is required.
2. Before you begin
Prerequisites
Make sure you have installed the following:
- GCC. Follow instructions here.
- Git: installation instructions here.
- Rust, version 1.88.0. Follow installation instructions here.
Get the code
So that you don't have to start entirely from scratch, this codelab provides a scaffold of the application's source code for you to complete. The following steps will show you how to finish the application, including using the protocol buffer compiler plugins to generate the boilerplate gRPC code.
First, create the codelab working directory and cd into it:
mkdir grpc-rust-getting-started && cd grpc-rust-getting-started
Download and extract the codelab:
curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/2026.tar.gz \
| tar xvz --strip-components=4 \
grpc-codelabs-2026/codelabs/grpc-rust-getting-started/start_here
Alternatively, you can download the .zip file containing only the codelab directory and manually unzip it.
The completed source code is available on GitHub if you want to skip typing in an implementation.
3. Define the service
Your first step is to define the application's gRPC service, its RPC method, and its request and response message types using Protocol Buffers. Your service will provide:
- An RPC method called
GetFeaturethat the server implements and the client calls. - The message types
PointandFeaturethat are data structures exchanged between the client and server when using theGetFeaturemethod. The client provides map coordinates as aPointin itsGetFeaturerequest to the server, and the server replies with a correspondingFeaturethat describes whatever is located at those coordinates.
This RPC method and its message types will all be defined in the proto/routeguide.proto file of the provided source code.
Protocol Buffers are commonly known as protobufs. For more information on gRPC terminology, see gRPC's Core concepts, architecture, and lifecycle.
Service method
Let's first define our service methods and then define our message types Point and Feature. The proto/routeguide.proto file has a service structure named RouteGuide that defines one or more methods provided by the application's service.
Add the rpc method GetFeature inside the RouteGuide definition. As explained earlier, this method will look up the name or address of a location from a given set of coordinates, so have GetFeature return a Feature for a given Point:
service RouteGuide {
// Definition of the service goes here
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
}
This is a unary RPC method: a simple RPC where the client sends a request to the server and waits for a response to come back, just like a local function call.
Message types
In the proto/routeguide.proto file of the source code, first define the Point message type. A Point represents a latitude-longitude coordinate pair on a map. For this codelab, use integers for the coordinates:
message Point {
int32 latitude = 1;
int32 longitude = 2;
}
The numbers 1 and 2 are unique ID numbers for each of the fields in the message structure.
Next, define the Feature message type. A Feature uses a string field for the name or postal address of something at a location specified by a Point:
message Feature {
// The name or address of the feature.
string name = 1;
// The point where the feature is located.
Point location = 2;
}
4. Generate the client and server code
We have already given you the generated code from the .proto file in the generated/ directory, including all the additions you made above. However, we'd like to take a moment to explain how the code generation works.
Our .proto file describes all the structs and functions that a client or server uses. We use a Cargo build script (build.rs) along with the grpc-protobuf-build crate to automatically generate this code.
In Cargo.toml we add grpc-protobuf-build under [build-dependencies].
In build.rs, we configure grpc_protobuf_build::CodeGen to compile proto/routeguide.proto into the generated/ directory. The key lines are here:
grpc_protobuf_build::CodeGen::new()
.include("proto")
.input("routeguide.proto")
.output_dir("generated")
.compile()
.unwrap();
This calls the grpc_protobuf_build crate's code generation, passing it the routeguide.proto. We have wrapped this in some code to only run when a feature flag is passed, so that it is only regenerated when you want it to be. You don't have to run this now, since we've already generated the code for you.
cargo build --bin routeguide-server --features regenerate_proto
When you run cargo build, build.rs compiles the protocol buffer definitions into the generate/ directory, including:
- Struct definitions for message types
PointandFeature. - A Tonic service trait we'll need to implement for the server:
route_guide_server::RouteGuide. - A gRPC-Rust client type we'll use to call the server:
route_guide_client::RouteGuideClient<T>.
You can refer to protoc-gen-rust-grpc guide for more information.
Next, we'll implement the service methods on the server.
5. Implement the service
In src/server/server.rs, we can bring the generated code into scope through gRPC's include_generated_proto! macro and import the RouteGuide trait and Point.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::{
route_guide_server::{RouteGuideServer, RouteGuide},
Point, Feature,
};
We can start by defining a struct to represent our service, we can do this on src/server/server.rs for now:
#[derive(Debug)]
pub struct RouteGuideService {
features: Vec<Feature>,
}
Now, we need to implement the route_guide_server::RouteGuide trait from our generated code.
Simple Unary RPC
The RouteGuideService implements all of our service methods. The get_feature function on the server side is where the main work is done: this takes a Point message from the client and returns in a Feature message the corresponding location information from a list of known places. Here's the function's implementation in src/server/server.rs:
#[tonic::async_trait]
impl RouteGuide for RouteGuideService {
async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
println!("GetFeature = {:?}", request);
let requested_point = request.get_ref();
for feature in self.features.iter() {
if feature.location().latitude() == requested_point.latitude() {
if feature.location().longitude() == requested_point.longitude(){
return Ok(Response::new(feature.clone()))
};
};
}
Ok(Response::new(Feature::default()))
}
}
Once we've implemented this method, we also need to start up a gRPC server so that clients can actually use our service. Replace main()with this.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:10000".parse().unwrap();
println!("RouteGuideServer listening on: {addr}");
let route_guide = RouteGuideService {
features: load(),
};
let svc = RouteGuideServer::new(route_guide);
Server::builder().add_service(svc).serve(addr).await?;
Ok(())
}
Here's what is happening in main(), step by step:
- Specify the port we want to use to listen for client requests
- Create a
RouteGuideServicewith features loaded in by calling the helper functionload() - Create an instance of the gRPC server using
RouteGuideServer::new()using the service we created. - Register our service implementation with the gRPC server.
- Call
serve()on the server with our port details to do a blocking wait until the process is killed.
6. Create the client
In this section, we'll look at creating a Rust client for our RouteGuide service in src/client/client.rs.
As we did in src/server/server.rs, we can bring the generated code into scope through gRPC's include_generated_proto! macro and import the RouteGuideClient type.
mod grpc_pb {
grpc::include_generated_proto!("generated", "routeguide");
}
use grpc_pb::{
route_guide_client::RouteGuideClient,
Point,
};
Call service methods
In gRPC-Rust, RPCs are asynchronous and non-blocking, using Rust's async/await syntax to await responses from the server.
To call service methods, we first create a Channel using Channel::builder(), specifying the server address (dns:///[::1]:10000) and the connection credentials (LocalChannelCredentials). We then pass the channel to RouteGuideClient::new() to instantiate our client:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new gRPC channel
let channel = Channel::builder(
"dns:///[::1]:10000",
Arc::new(LocalChannelCredentials::new()),
)
.build();
// Create a new client
let client = RouteGuideClient::new(channel);
}
In this function, RouteGuideClient::new() binds the generic channel to the generated client stub that implements the methods defined in our .proto service definition.
Simple RPC
Calling the simple RPC GetFeature is as straightforward as calling a local method. Add this in main():
println!("*** SIMPLE RPC ***");
let point = proto!(Point {
latitude: 409_146_138,
longitude: -746_188_906,
});
let response = client
.get_feature(point)
.await
.expect("RPC error");
In gRPC-Rust, we pass the protobuf message Point directly to client.get_feature(point). Awaiting the returned future yields the Feature response directly, without needing further method calls.
Next, print the fields from the response:
println!(
"Response = Name = \"{}\", Latitude = {}, Longitude = {}",
response.name(),
response.location().latitude(),
response.location().longitude()
);
In all, the client's main() function should look like this:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Create a new gRPC channel
let channel = Channel::builder(
"dns:///[::1]:10000",
Arc::new(LocalChannelCredentials::new()),
)
.build();
// 2. Instantiate the generated RouteGuideClient
let client = RouteGuideClient::new(channel);
println!("*** SIMPLE RPC ***");
let point = proto!(Point {
latitude: 409_146_138,
longitude: -746_188_906,
});
let response = client
.get_feature(point)
.await
.expect("RPC error");
println!(
"Response = Name = \"{}\", Latitude = {}, Longitude = {}",
response.name(),
response.location().latitude(),
response.location().longitude()
);
Ok(())
}
7. Try it out
To run your client and server, first verify that both binary targets are defined in Cargo.toml:
[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"
[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"
Then, execute the following commands from our working directory:
- Run the server in one terminal:
cargo run --bin routeguide-server
- Run the client from another terminal:
cargo run --bin routeguide-client
You'll see output like this, with timestamps omitted for clarity:
*** SIMPLE RPC ***
Response = Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Latitude = 409146138, Longitude = -746188906
8. What's next
- Continue to the Getting Started with gRPC-Rust (Streaming) codelab.
- Explore the official gRPC-Rust Repository.
- Learn more about gRPC architecture in Core Concepts.
- View the gRPC-Rust documentation on gRPC.io.
9. Contributors to this Codelab
- Cathy Zhao
- Lucio Franco
- Arvind Bright
- Nathaniel Ford