Comienza a usar gRPC-Rust

1. Introducción

En este codelab, usarás gRPC-Rust para crear un cliente y un servidor que formen la base de una aplicación de asignación de rutas escrita en Rust.

Al final del instructivo, tendrás un cliente que se conectará a un servidor remoto con la implementación oficial de Rust del protocolo gRPC para recuperar el nombre o la dirección postal de una ubicación en coordenadas específicas de un mapa. Una aplicación completa podría usar este diseño de cliente-servidor para enumerar o resumir puntos de interés a lo largo de una ruta.

El servicio se define en un archivo de búferes de protocolo, que se usará para generar código estándar para el cliente y el servidor, de modo que puedan comunicarse entre sí, lo que te ahorrará tiempo y esfuerzo en la implementación de esa funcionalidad.

Este código generado se ocupa no solo de las complejidades de la comunicación entre el servidor y el cliente, sino también de la serialización y deserialización de datos.

Qué aprenderás

  • Cómo usar búferes de protocolo para definir una API de servicio
  • Cómo compilar un cliente basado en gRPC a partir de una definición de búfer de protocolo con la generación de código automatizada
  • Una comprensión de la comunicación entre cliente y servidor con gRPC

Este codelab está dirigido a desarrolladores de Rust que no conocen gRPC o que desean repasar gRPC, o cualquier otra persona interesada en compilar sistemas distribuidos. No se requiere experiencia previa con gRPC.

2. Antes de comenzar

Requisitos previos

Asegúrate de haber instalado lo siguiente:

  • GCC. Sigue las instrucciones que aparecen aquí.
  • Git: instrucciones de instalación aquí.
  • Rust, versión 1.88.0. Sigue las instrucciones de instalación que aparecen aquí.

Obtén el código

Para que no tengas que comenzar desde cero, este codelab proporciona un andamio del código fuente de la aplicación para que lo completes. En los siguientes pasos, se mostrará cómo finalizar la aplicación, incluido el uso de los complementos del compilador de búferes de protocolo para generar el código gRPC estándar.

Primero, crea el directorio de trabajo del codelab y cd en él:

mkdir grpc-rust-getting-started && cd grpc-rust-getting-started

Descarga y extrae el 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

Como alternativa, puedes descargar el archivo .zip que contiene solo el directorio del codelab y descomprimirlo de forma manual.

El código fuente completo está disponible en GitHub si deseas omitir la escritura de una implementación.

3. Define el servicio

El primer paso es definir el servicio gRPC de la aplicación, su método RPC y sus tipos de mensajes de solicitud y respuesta con búferes de protocolo. Tu servicio proporcionará lo siguiente:

  • Un método RPC llamado GetFeature que el servidor implementa y el cliente llama
  • Los tipos de mensajes Point y Feature que son estructuras de datos intercambiadas entre el cliente y el servidor cuando se usa el método GetFeature El cliente proporciona coordenadas de mapa como un Point en su solicitud GetFeature al servidor, y el servidor responde con una Feature correspondiente que describe lo que se encuentra en esas coordenadas.

Este método RPC y sus tipos de mensajes se definirán en el archivo proto/routeguide.proto del código fuente proporcionado.

Los búferes de protocolo se conocen comúnmente como protobufs. Para obtener más información sobre la terminología de gRPC, consulta Conceptos básicos, arquitectura y ciclo de vida de gRPC.

Método de servicio

Primero, definamos nuestros métodos de servicio y, luego, definamos nuestros tipos de mensajes Point y Feature. El archivo proto/routeguide.proto tiene una estructura service llamada RouteGuide que define uno o más métodos proporcionados por el servicio de la aplicación.

Agrega el método rpc GetFeature dentro de la definición RouteGuide. Como se explicó anteriormente, este método buscará el nombre o la dirección de una ubicación a partir de un conjunto de coordenadas determinado, por lo que GetFeature mostrará una Feature para un Point determinado:

service RouteGuide {
  // Definition of the service goes here

  // Obtains the feature at a given position.
  rpc GetFeature(Point) returns (Feature) {}
}

Este es un método RPC unario: una RPC simple en la que el cliente envía una solicitud al servidor y espera a que vuelva una respuesta, al igual que una llamada a función local.

Tipos de mensajes

En el archivo proto/routeguide.proto del código fuente, primero define el tipo de mensaje Point. Un Point representa un par de coordenadas de latitud y longitud en un mapa. Para este codelab, usa números enteros para las coordenadas:

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

Los números 1 y 2 son números de ID únicos para cada uno de los campos de la estructura message.

A continuación, define el tipo de mensaje Feature. Una Feature usa un campo string para el nombre o la dirección postal de algo en una ubicación especificada por un Point:

message Feature {
  // The name or address of the feature.
  string name = 1;

  // The point where the feature is located.
  Point location = 2;
}

4. Genera el código del cliente y del servidor

Ya te proporcionamos el código generado del archivo .proto en el directorio generated/, incluidas todas las adiciones que realizaste anteriormente. Sin embargo, nos gustaría tomar un momento para explicar cómo funciona la generación de código.

Nuestro archivo .proto describe todas las estructuras y funciones que usa un cliente o servidor. Usamos una secuencia de comandos de compilación de Cargo (build.rs) junto con el crate grpc-protobuf-build para generar este código automáticamente.

En Cargo.toml, agregamos grpc-protobuf-build en [build-dependencies].

En build.rs, configuramos grpc_protobuf_build::CodeGen para compilar proto/routeguide.proto en el directorio generated/. Las líneas clave son las siguientes:

grpc_protobuf_build::CodeGen::new()
    .include("proto")
    .input("routeguide.proto")
    .output_dir("generated")
    .compile()
    .unwrap();

Esto llama a la generación de código del crate grpc_protobuf_build y le pasa el routeguide.proto. Lo incluimos en un código para que solo se ejecute cuando se pasa una marca de función, de modo que solo se vuelva a generar cuando lo desees. No tienes que ejecutarlo ahora, ya que ya generamos el código por ti.

cargo build --bin routeguide-server --features regenerate_proto

Cuando ejecutas la compilación de Cargo, build.rs compila las definiciones de búferes de protocolo en el directorio generate/, incluidos los siguientes elementos:

  • Definiciones de estructuras para los tipos de mensajes Point y Feature
  • Un rasgo de servicio de Tonic que deberemos implementar para el servidor: route_guide_server::RouteGuide
  • Un tipo de cliente de gRPC-Rust que usaremos para llamar al servidor: route_guide_client::RouteGuideClient<T>

Puedes consultar la guía de protoc-gen-rust-grpc para obtener más información.

A continuación, implementaremos los métodos de servicio en el servidor.

5. Implementa el servicio

En src/server/server.rs, podemos incluir el código generado en el alcance a través de la macro include_generated_proto! de gRPC y, luego, importar el rasgo RouteGuide y Point.

mod grpc_pb {
    grpc::include_generated_proto!("generated", "routeguide");
}

use grpc_pb::{
    route_guide_server::{RouteGuideServer, RouteGuide},
    Point, Feature,
};

Podemos comenzar definiendo una estructura para representar nuestro servicio. Por ahora, podemos hacerlo en src/server/server.rs:

#[derive(Debug)]
pub struct RouteGuideService {
    features: Vec<Feature>,
}

Ahora, debemos implementar el rasgo route_guide_server::RouteGuide de nuestro código generado.

RPC unaria simple

RouteGuideService implementa todos nuestros métodos de servicio. La función get_feature en el servidor es donde se realiza el trabajo principal: toma un mensaje Point del cliente y muestra en un mensaje Feature la información de ubicación correspondiente de una lista de lugares conocidos. Esta es la implementación de la función en 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()))
    }
}

Una vez que implementamos este método, también debemos iniciar un servidor gRPC para que los clientes puedan usar nuestro servicio. Reemplaza main() por lo siguiente.

#[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(())
}

Esto es lo que sucede en main(), paso a paso:

  1. Especifica el puerto que queremos usar para escuchar las solicitudes del cliente.
  2. Crea un RouteGuideService con funciones cargadas llamando a la función auxiliar load().
  3. Crea una instancia del servidor gRPC con RouteGuideServer::new() usando el servicio que creamos.
  4. Registra nuestra implementación de servicio con el servidor gRPC.
  5. Llama a serve() en el servidor con los detalles de nuestro puerto para realizar una espera de bloqueo hasta que se finalice el proceso.

6. Crea el cliente

En esta sección, veremos cómo crear un cliente de Rust para nuestro servicio RouteGuide en src/client/client.rs.

Al igual que en src/server/server.rs, podemos incluir el código generado en el alcance a través de la macro include_generated_proto! de gRPC y, luego, importar el tipo RouteGuideClient.

mod grpc_pb {
    grpc::include_generated_proto!("generated", "routeguide");
}

use grpc_pb::{
    route_guide_client::RouteGuideClient,
    Point,
};

Llama a métodos de servicio

En gRPC-Rust, las RPC son asíncronas y no bloquean, y usan la sintaxis async/await de Rust para esperar respuestas del servidor.

Para llamar a los métodos de servicio, primero creamos un Channel con Channel::builder(), especificando la dirección del servidor (dns:///[::1]:10000) y las credenciales de conexión (LocalChannelCredentials). Luego, pasamos el canal a RouteGuideClient::new() para crear una instancia de nuestro cliente:

#[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);



}

En esta función, RouteGuideClient::new() vincula el canal genérico al stub de cliente generado que implementa los métodos definidos en nuestra definición de servicio .proto.

RPC simple

Llamar a la RPC simple GetFeature es tan sencillo como llamar a un método local. Agrega esto en 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");

En gRPC-Rust, pasamos el mensaje protobuf Point directamente a client.get_feature(point). Esperar el futuro que se muestra produce la respuesta Feature directamente, sin necesidad de más llamadas al método.

A continuación, imprime los campos de la respuesta:

println!(
    "Response = Name = \"{}\", Latitude = {}, Longitude = {}",
    response.name(),
    response.location().latitude(),
    response.location().longitude()
);

En total, la función main() del cliente debería verse de la siguiente manera:

#[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. Probar

Para ejecutar tu cliente y servidor, primero verifica que ambos destinos binarios estén definidos en Cargo.toml:

[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"

[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"

Luego, ejecuta los siguientes comandos desde nuestro directorio de trabajo:

  1. Ejecuta el servidor en una terminal:
cargo run --bin routeguide-server
  1. Ejecuta el cliente desde otra terminal:
cargo run --bin routeguide-client

Verás un resultado como este, con marcas de tiempo omitidas para mayor claridad:

*** SIMPLE RPC ***
Response = Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Latitude = 409146138, Longitude = -746188906

8. ¿Qué sigue?

9. Colaboradores de este codelab

  • Cathy Zhao
  • Lucio Franco
  • Arvind Bright
  • Nathaniel Ford