Primeiros passos com o gRPC-Rust

1. Introdução

Neste codelab, você vai usar o gRPC-Rust para criar um cliente e um servidor que formam a base de um aplicativo de mapeamento de rotas escrito em Rust.

Ao final do tutorial, você terá um cliente que se conecta a um servidor remoto usando a implementação oficial do protocolo gRPC em Rust para buscar o nome ou o endereço postal de um local em coordenadas específicas em um mapa. Um aplicativo completo pode usar esse design de cliente-servidor para enumerar ou resumir pontos de interesse ao longo de uma rota.

O serviço é definido em um arquivo de buffers de protocolo, que será usado para gerar código boilerplate para o cliente e o servidor, para que eles possam se comunicar entre si, economizando tempo e esforço na implementação dessa funcionalidade.

Esse código gerado cuida não apenas das complexidades da comunicação entre o servidor e o cliente, mas também da serialização e desserialização de dados.

O que você vai aprender

  • Como usar buffers de protocolo para definir uma API de serviço.
  • Como criar um cliente baseado em gRPC a partir de uma definição de buffer de protocolo usando a geração de código automatizada.
  • Uma compreensão da comunicação cliente-servidor com gRPC.

Este codelab é destinado a desenvolvedores de Rust que não conhecem o gRPC ou que querem se atualizar sobre o gRPC, ou qualquer outra pessoa interessada em criar sistemas distribuídos. Não é necessário ter experiência anterior com o gRPC.

2. Antes de começar

Pré-requisitos

Instale o seguinte:

  • GCC. Siga as instruções aqui.
  • Git: instruções de instalação aqui.
  • Rust, versão 1.88.0. Siga as instruções de instalação aqui.

Acessar o código

Para que você não precise começar do zero, este codelab fornece um scaffold do código-fonte do aplicativo para você concluir. As etapas a seguir mostram como concluir o aplicativo, incluindo o uso dos plug-ins do compilador de buffer de protocolo para gerar o código gRPC boilerplate.

Primeiro, crie o diretório de trabalho do codelab e cd nele:

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

Faça o download e extraia o 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, você pode fazer o download do arquivo .zip que contém apenas o diretório do codelab e descompactá-lo manualmente.

O código-fonte concluído está disponível no GitHub se você quiser pular a digitação de uma implementação.

3. Definir o serviço

A primeira etapa é definir o serviço gRPC do aplicativo, o método RPC e os tipos de mensagem de solicitação e resposta usando buffers de protocolo. Seu serviço vai fornecer:

  • Um método RPC chamado GetFeature que o servidor implementa e o cliente chama.
  • Os tipos de mensagem Point e Feature, que são estruturas de dados trocadas entre o cliente e o servidor ao usar o método GetFeature. O cliente fornece coordenadas de mapa como um Point na solicitação GetFeature ao servidor, e o servidor responde com um Feature correspondente que descreve o que está localizado nessas coordenadas.

Esse método RPC e os tipos de mensagem serão definidos no arquivo proto/routeguide.proto do código-fonte fornecido.

Os buffers de protocolo são conhecidos como protobufs. Para mais informações sobre a terminologia do gRPC, consulte Conceitos básicos, arquitetura e ciclo de vida do gRPC.

Método de serviço

Vamos primeiro definir nossos métodos de serviço e, em seguida, definir os tipos de mensagem Point e Feature. O arquivo proto/routeguide.proto tem uma estrutura service chamada RouteGuide que define um ou mais métodos fornecidos pelo serviço do aplicativo.

Adicione o método rpc GetFeature dentro da definição RouteGuide. Como explicado anteriormente, esse método vai procurar o nome ou o endereço de um local em um determinado conjunto de coordenadas. Portanto, faça com que GetFeature retorne um Feature para um determinado Point:

service RouteGuide {
  // Definition of the service goes here

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

Esse é um método RPC unário: um RPC simples em que o cliente envia uma solicitação ao servidor e aguarda uma resposta, assim como uma chamada de função local.

Tipos de mensagem

No arquivo proto/routeguide.proto do código-fonte, defina primeiro o tipo de mensagem Point. Um Point representa um par de coordenadas de latitude e longitude em um mapa. Para este codelab, use números inteiros para as coordenadas:

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

Os números 1 e 2 são números de ID exclusivos para cada um dos campos na estrutura message.

Em seguida, defina o tipo de mensagem Feature. Um Feature usa um campo string para o nome ou endereço postal de algo em um local especificado por um Point:

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

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

4. Gerar o código do cliente e do servidor

Já fornecemos o código gerado do arquivo .proto no diretório generated/, incluindo todas as adições feitas acima. No entanto, gostaríamos de explicar como a geração de código funciona.

Nosso arquivo .proto descreve todas as structs e funções usadas por um cliente ou servidor. Usamos um script de build do Cargo (build.rs) com o crate grpc-protobuf-build para gerar esse código automaticamente.

Em Cargo.toml, adicionamos grpc-protobuf-build em [build-dependencies].

Em build.rs, configuramos grpc_protobuf_build::CodeGen para compilar proto/routeguide.proto no diretório generated/. As linhas principais estão aqui:

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

Isso chama a geração de código do crate grpc_protobuf_build, transmitindo o routeguide.proto. Envolvemos isso em algum código para ser executado apenas quando uma flag de recurso é transmitida, para que ela seja regenerada apenas quando você quiser. Não é necessário executar isso agora, já que geramos o código para você.

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

Quando você executa o build do Cargo, build.rs compila as definições de buffer de protocolo no diretório generate/, incluindo:

  • Definições de struct para tipos de mensagem Point e Feature.
  • Um trait de serviço do Tonic que precisaremos implementar para o servidor: route_guide_server::RouteGuide.
  • Um tipo de cliente gRPC-Rust que usaremos para chamar o servidor: route_guide_client::RouteGuideClient<T>.

Consulte o guia protoc-gen-rust-grpc para mais informações.

Em seguida, vamos implementar os métodos de serviço no servidor.

5. Implementar o serviço

Em src/server/server.rs, podemos colocar o código gerado no escopo usando a macro include_generated_proto! do gRPC e importar o trait RouteGuide e Point.

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

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

Podemos começar definindo uma struct para representar nosso serviço. Podemos fazer isso em src/server/server.rs por enquanto:

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

Agora, precisamos implementar o trait route_guide_server::RouteGuide do nosso código gerado.

RPC unário simples

O RouteGuideService implementa todos os nossos métodos de serviço. A função get_feature no lado do servidor é onde o trabalho principal é feito: ela recebe uma mensagem Point do cliente e retorna em uma mensagem Feature as informações de local correspondentes de uma lista de lugares conhecidos. Confira a implementação da função em 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()))
    }
}

Depois de implementar esse método, também precisamos iniciar um servidor gRPC para que os clientes possam usar nosso serviço. Substitua main() por este.

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

Confira o que está acontecendo em main(), etapa por etapa:

  1. Especifique a porta que queremos usar para detectar solicitações de clientes.
  2. Crie um RouteGuideService com recursos carregados chamando a função auxiliar load().
  3. Crie uma instância do servidor gRPC usando RouteGuideServer::new() com o serviço que criamos.
  4. Registre nossa implementação de serviço com o servidor gRPC.
  5. Chame serve() no servidor com os detalhes da porta para fazer uma espera de bloqueio até que o processo seja encerrado.

6. Criar o cliente

Nesta seção, vamos analisar a criação de um cliente Rust para nosso serviço RouteGuide em src/client/client.rs.

Como fizemos em src/server/server.rs, podemos colocar o código gerado no escopo usando a macro include_generated_proto! do gRPC e importar o tipo RouteGuideClient.

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

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

Chamar métodos de serviço

No gRPC-Rust, os RPCs são assíncronos e não bloqueadores, usando a sintaxe async/await do Rust para aguardar respostas do servidor.

Para chamar métodos de serviço, primeiro criamos um Channel usando Channel::builder(), especificando o endereço do servidor (dns:///[::1]:10000) e as credenciais de conexão (LocalChannelCredentials). Em seguida, transmitimos o canal para RouteGuideClient::new() para instanciar nosso 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);



}

Nessa função, RouteGuideClient::new() vincula o canal genérico ao stub do cliente gerado que implementa os métodos definidos na nossa definição de serviço .proto.

RPC simples

Chamar o RPC simples GetFeature é tão simples quanto chamar um método local. Adicione isso em 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");

No gRPC-Rust, transmitimos a mensagem protobuf Point diretamente para client.get_feature(point). Aguardar o futuro retornado produz a resposta Feature diretamente, sem precisar de mais chamadas de método.

Em seguida, imprima os campos da resposta:

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

No total, a função main() do cliente deve ser assim:

#[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. Faça um teste

Para executar o cliente e o servidor, primeiro verifique se os dois destinos binários estão definidos em Cargo.toml:

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

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

Em seguida, execute os seguintes comandos no nosso diretório de trabalho:

  1. Execute o servidor em um terminal:
cargo run --bin routeguide-server
  1. Execute o cliente em outro terminal:
cargo run --bin routeguide-client

Você verá uma saída como esta, com carimbos de data/hora omitidos para fins de esclarecimento:

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

8. A seguir

9. Colaboradores deste codelab

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