Prost: Protocol Buffers em Rust

Guia completo da crate prost para Protocol Buffers em Rust. Aprenda code generation com prost-build, tipos de mensagem, enums, oneof, build.rs, integração com Tonic/gRPC e exemplos práticos de serialização.

A crate prost e a implementacao mais popular de Protocol Buffers (protobuf) para Rust. Protocol Buffers e um formato de serializacao binario desenvolvido pelo Google, amplamente utilizado em microsservicos, APIs gRPC, armazenamento de dados e comunicacao entre sistemas. O prost gera codigo Rust idiomatico a partir de arquivos .proto, com structs nativas, enums, e implementacoes de Message para serializacao/deserializacao eficiente.

Diferente de alternativas como protobuf-rs (que usa uma API mais proxima do C++), o prost gera codigo Rust limpo e idiomatico: structs com campos publicos, enums Rust reais, e integracao natural com o ecossistema (serde, etc.). E a escolha padrao para projetos que usam gRPC com Tonic.

Instalação

Adicione ao seu Cargo.toml:

[dependencies]
prost = "0.13"
prost-types = "0.13"  # Tipos well-known (Timestamp, Duration, etc.)

[build-dependencies]
prost-build = "0.13"

Se voce vai usar gRPC com Tonic:

[dependencies]
prost = "0.13"
tonic = "0.12"
tokio = { version = "1", features = ["full"] }

[build-dependencies]
tonic-build = "0.12"

Uso Básico

Definindo Mensagens Proto

Crie o arquivo proto/mensagens.proto:

syntax = "proto3";

package meuapp;

// Mensagem simples
message Usuario {
  uint64 id = 1;
  string nome = 2;
  string email = 3;
  int32 idade = 4;
  Endereco endereco = 5;
  repeated string telefones = 6;
  Status status = 7;
}

message Endereco {
  string rua = 1;
  string cidade = 2;
  string estado = 3;
  string cep = 4;
  string pais = 5;
}

enum Status {
  STATUS_DESCONHECIDO = 0;
  STATUS_ATIVO = 1;
  STATUS_INATIVO = 2;
  STATUS_BLOQUEADO = 3;
}

// Mensagem com oneof
message Notificacao {
  uint64 id = 1;
  string destinatario = 2;

  oneof conteudo {
    string texto = 3;
    EmailNotificacao email = 4;
    PushNotificacao push = 5;
  }
}

message EmailNotificacao {
  string assunto = 1;
  string corpo_html = 2;
  repeated string anexos = 3;
}

message PushNotificacao {
  string titulo = 1;
  string corpo = 2;
  string icone = 3;
}

Configurando o build.rs

Crie build.rs na raiz do projeto:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    prost_build::compile_protos(
        &["proto/mensagens.proto"],
        &["proto/"],
    )?;
    Ok(())
}

Usando as Mensagens Geradas

// O prost gera codigo em OUT_DIR, incluimos com include!
pub mod meuapp {
    include!(concat!(env!("OUT_DIR"), "/meuapp.rs"));
}

use meuapp::{Endereco, Notificacao, Status, Usuario};
use prost::Message;

fn main() {
    // Criar um usuario
    let usuario = Usuario {
        id: 1,
        nome: "Maria Silva".to_string(),
        email: "maria@exemplo.com".to_string(),
        idade: 30,
        endereco: Some(Endereco {
            rua: "Rua das Flores, 123".to_string(),
            cidade: "Sao Paulo".to_string(),
            estado: "SP".to_string(),
            cep: "01310-100".to_string(),
            pais: "Brasil".to_string(),
        }),
        telefones: vec![
            "(11) 98765-4321".to_string(),
            "(11) 3456-7890".to_string(),
        ],
        status: Status::Ativo as i32,
    };

    // Serializar para bytes (protobuf binario)
    let mut buf = Vec::new();
    usuario.encode(&mut buf).unwrap();
    println!("Tamanho serializado: {} bytes", buf.len());

    // Deserializar de bytes
    let usuario_decodificado = Usuario::decode(buf.as_slice()).unwrap();
    println!("Nome: {}", usuario_decodificado.nome);
    println!("Email: {}", usuario_decodificado.email);
    println!("Telefones: {:?}", usuario_decodificado.telefones);

    if let Some(endereco) = &usuario_decodificado.endereco {
        println!("Cidade: {}", endereco.cidade);
    }

    // Verificar igualdade
    assert_eq!(usuario, usuario_decodificado);
    println!("Serializacao/deserializacao OK!");
}

Trabalhando com Enums

use meuapp::Status;

fn descrever_status(status: i32) -> &'static str {
    match Status::try_from(status) {
        Ok(Status::Desconhecido) => "Desconhecido",
        Ok(Status::Ativo) => "Ativo",
        Ok(Status::Inativo) => "Inativo",
        Ok(Status::Bloqueado) => "Bloqueado",
        Err(_) => "Valor invalido",
    }
}

fn main() {
    let status = Status::Ativo;
    println!("Status: {} ({})", descrever_status(status as i32), status as i32);

    // Converter de i32
    let status_de_int = Status::try_from(2);
    println!("Status 2: {:?}", status_de_int); // Ok(Inativo)

    // Valor invalido
    let invalido = Status::try_from(99);
    println!("Status 99: {:?}", invalido); // Err(99)
}

Trabalhando com Oneof

use meuapp::{
    notificacao::Conteudo, EmailNotificacao, Notificacao, PushNotificacao,
};
use prost::Message;

fn criar_notificacao_texto() -> Notificacao {
    Notificacao {
        id: 1,
        destinatario: "usuario@exemplo.com".to_string(),
        conteudo: Some(Conteudo::Texto(
            "Sua encomenda foi enviada!".to_string(),
        )),
    }
}

fn criar_notificacao_email() -> Notificacao {
    Notificacao {
        id: 2,
        destinatario: "usuario@exemplo.com".to_string(),
        conteudo: Some(Conteudo::Email(EmailNotificacao {
            assunto: "Confirmacao de Pedido #12345".to_string(),
            corpo_html: "<h1>Pedido Confirmado</h1><p>Obrigado!</p>".to_string(),
            anexos: vec!["nota_fiscal.pdf".to_string()],
        })),
    }
}

fn criar_notificacao_push() -> Notificacao {
    Notificacao {
        id: 3,
        destinatario: "device_token_abc123".to_string(),
        conteudo: Some(Conteudo::Push(PushNotificacao {
            titulo: "Nova mensagem".to_string(),
            corpo: "Voce tem uma nova mensagem de Maria".to_string(),
            icone: "message_icon.png".to_string(),
        })),
    }
}

fn processar_notificacao(notif: &Notificacao) {
    println!("Notificacao #{} para {}", notif.id, notif.destinatario);

    match &notif.conteudo {
        Some(Conteudo::Texto(texto)) => {
            println!("  Tipo: Texto");
            println!("  Conteudo: {}", texto);
        }
        Some(Conteudo::Email(email)) => {
            println!("  Tipo: Email");
            println!("  Assunto: {}", email.assunto);
            println!("  Anexos: {:?}", email.anexos);
        }
        Some(Conteudo::Push(push)) => {
            println!("  Tipo: Push");
            println!("  Titulo: {}", push.titulo);
            println!("  Corpo: {}", push.corpo);
        }
        None => {
            println!("  Sem conteudo!");
        }
    }
}

fn main() {
    let notificacoes = vec![
        criar_notificacao_texto(),
        criar_notificacao_email(),
        criar_notificacao_push(),
    ];

    for notif in &notificacoes {
        processar_notificacao(notif);

        // Serializar e deserializar
        let mut buf = Vec::new();
        notif.encode(&mut buf).unwrap();
        let decodificada = Notificacao::decode(buf.as_slice()).unwrap();
        assert_eq!(notif, &decodificada);
        println!("  Serializado: {} bytes\n", buf.len());
    }
}

Recursos Avançados

Configuração Avançada do build.rs

use std::io::Result;

fn main() -> Result<()> {
    let mut config = prost_build::Config::new();

    // Adicionar derives customizados a todas as mensagens
    config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");

    // Adicionar atributos a tipos especificos
    config.type_attribute(
        ".meuapp.Usuario",
        "#[derive(Hash)]"
    );

    // Renomear campos
    config.field_attribute(
        ".meuapp.Usuario.email",
        "#[serde(rename = \"email_address\")]"
    );

    // Gerar em diretorio especifico (opcional)
    // config.out_dir("src/proto");

    // Compilar
    config.compile_protos(
        &[
            "proto/mensagens.proto",
            "proto/servicos.proto",
        ],
        &["proto/"],
    )?;

    Ok(())
}

Tipos Well-Known (prost-types)

use prost_types::{Duration, Timestamp};

fn main() {
    // Timestamp
    let agora = Timestamp {
        seconds: 1709913600, // epoch seconds
        nanos: 500_000_000,  // 0.5 segundos
    };
    println!("Timestamp: {}s {}ns", agora.seconds, agora.nanos);

    // Duration
    let duracao = Duration {
        seconds: 3600,
        nanos: 0,
    };
    println!("Duracao: {}s", duracao.seconds);

    // Converter de/para std::time
    use std::time::{SystemTime, UNIX_EPOCH};
    let system_time = SystemTime::now();
    let desde_epoch = system_time.duration_since(UNIX_EPOCH).unwrap();
    let timestamp = Timestamp {
        seconds: desde_epoch.as_secs() as i64,
        nanos: desde_epoch.subsec_nanos() as i32,
    };
    println!("Agora: {}s", timestamp.seconds);
}

Integração com Tonic (gRPC)

Defina servicos no proto (proto/servicos.proto):

syntax = "proto3";

package meuapp;

import "mensagens.proto";

service UsuarioService {
  rpc CriarUsuario(CriarUsuarioRequest) returns (CriarUsuarioResponse);
  rpc BuscarUsuario(BuscarUsuarioRequest) returns (Usuario);
  rpc ListarUsuarios(ListarUsuariosRequest) returns (stream Usuario);
  rpc AtualizarUsuarios(stream AtualizarUsuarioRequest) returns (AtualizarUsuariosResponse);
}

message CriarUsuarioRequest {
  string nome = 1;
  string email = 2;
  int32 idade = 3;
}

message CriarUsuarioResponse {
  uint64 id = 1;
  string mensagem = 2;
}

message BuscarUsuarioRequest {
  uint64 id = 1;
}

message ListarUsuariosRequest {
  int32 pagina = 1;
  int32 por_pagina = 2;
  Status filtro_status = 3;
}

message AtualizarUsuarioRequest {
  uint64 id = 1;
  string nome = 2;
  string email = 3;
}

message AtualizarUsuariosResponse {
  int32 atualizados = 1;
}

Atualize build.rs para Tonic:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    tonic_build::configure()
        .build_server(true)
        .build_client(true)
        .compile_protos(
            &["proto/servicos.proto"],
            &["proto/"],
        )?;
    Ok(())
}

Implementacao do servidor:

use tonic::{transport::Server, Request, Response, Status};

pub mod meuapp {
    tonic::include_proto!("meuapp");
}

use meuapp::{
    usuario_service_server::{UsuarioService, UsuarioServiceServer},
    BuscarUsuarioRequest, CriarUsuarioRequest, CriarUsuarioResponse,
    ListarUsuariosRequest, Usuario,
};

use std::collections::HashMap;
use std::sync::{Arc, Mutex};

#[derive(Debug)]
struct UsuarioServiceImpl {
    usuarios: Arc<Mutex<HashMap<u64, Usuario>>>,
    proximo_id: Arc<Mutex<u64>>,
}

impl UsuarioServiceImpl {
    fn novo() -> Self {
        UsuarioServiceImpl {
            usuarios: Arc::new(Mutex::new(HashMap::new())),
            proximo_id: Arc::new(Mutex::new(1)),
        }
    }
}

#[tonic::async_trait]
impl UsuarioService for UsuarioServiceImpl {
    async fn criar_usuario(
        &self,
        request: Request<CriarUsuarioRequest>,
    ) -> Result<Response<CriarUsuarioResponse>, Status> {
        let req = request.into_inner();
        let mut id_lock = self.proximo_id.lock().unwrap();
        let id = *id_lock;
        *id_lock += 1;

        let usuario = Usuario {
            id,
            nome: req.nome.clone(),
            email: req.email.clone(),
            idade: req.idade,
            endereco: None,
            telefones: vec![],
            status: meuapp::Status::Ativo as i32,
        };

        self.usuarios.lock().unwrap().insert(id, usuario);

        Ok(Response::new(CriarUsuarioResponse {
            id,
            mensagem: format!("Usuario '{}' criado com sucesso", req.nome),
        }))
    }

    async fn buscar_usuario(
        &self,
        request: Request<BuscarUsuarioRequest>,
    ) -> Result<Response<Usuario>, Status> {
        let id = request.into_inner().id;
        let usuarios = self.usuarios.lock().unwrap();

        match usuarios.get(&id) {
            Some(usuario) => Ok(Response::new(usuario.clone())),
            None => Err(Status::not_found(format!(
                "Usuario com id {} nao encontrado",
                id
            ))),
        }
    }

    type ListarUsuariosStream =
        tokio_stream::wrappers::ReceiverStream<Result<Usuario, Status>>;

    async fn listar_usuarios(
        &self,
        request: Request<ListarUsuariosRequest>,
    ) -> Result<Response<Self::ListarUsuariosStream>, Status> {
        let req = request.into_inner();
        let usuarios = self.usuarios.lock().unwrap().clone();

        let (tx, rx) = tokio::sync::mpsc::channel(128);

        tokio::spawn(async move {
            let pagina = req.pagina.max(1) as usize;
            let por_pagina = req.por_pagina.max(10) as usize;
            let inicio = (pagina - 1) * por_pagina;

            let mut lista: Vec<Usuario> = usuarios.values().cloned().collect();
            lista.sort_by_key(|u| u.id);

            for usuario in lista.into_iter().skip(inicio).take(por_pagina) {
                if tx.send(Ok(usuario)).await.is_err() {
                    break;
                }
            }
        });

        Ok(Response::new(tokio_stream::wrappers::ReceiverStream::new(rx)))
    }

    async fn atualizar_usuarios(
        &self,
        _request: Request<tonic::Streaming<meuapp::AtualizarUsuarioRequest>>,
    ) -> Result<Response<meuapp::AtualizarUsuariosResponse>, Status> {
        // Implementacao de streaming bidirecional
        Ok(Response::new(meuapp::AtualizarUsuariosResponse {
            atualizados: 0,
        }))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "0.0.0.0:50051".parse()?;
    let servico = UsuarioServiceImpl::novo();

    println!("Servidor gRPC ouvindo em {}", addr);

    Server::builder()
        .add_service(UsuarioServiceServer::new(servico))
        .serve(addr)
        .await?;

    Ok(())
}

Serialização Eficiente com Bytes

use bytes::{Bytes, BytesMut};
use prost::Message;

pub mod meuapp {
    include!(concat!(env!("OUT_DIR"), "/meuapp.rs"));
}

use meuapp::Usuario;

fn serializar_eficiente(usuario: &Usuario) -> Bytes {
    let mut buf = BytesMut::with_capacity(usuario.encoded_len());
    usuario.encode(&mut buf).unwrap();
    buf.freeze()
}

fn deserializar_eficiente(dados: Bytes) -> Result<Usuario, prost::DecodeError> {
    Usuario::decode(dados)
}

fn main() {
    let usuario = Usuario {
        id: 42,
        nome: "Maria".to_string(),
        email: "maria@exemplo.com".to_string(),
        idade: 30,
        endereco: None,
        telefones: vec![],
        status: 1,
    };

    // Serializar
    let bytes = serializar_eficiente(&usuario);
    println!("Tamanho: {} bytes", bytes.len());

    // Comparar com JSON
    let json = serde_json::to_string(&usuario).unwrap_or_default();
    println!("Tamanho JSON: {} bytes", json.len());
    println!("Reducao: {:.1}%", (1.0 - bytes.len() as f64 / json.len() as f64) * 100.0);

    // Deserializar
    let decodificado = deserializar_eficiente(bytes).unwrap();
    assert_eq!(usuario.nome, decodificado.nome);
}

Boas Práticas

1. Organize seus Arquivos Proto

projeto/
├── proto/
│   ├── meuapp/
│   │   ├── mensagens.proto
│   │   ├── servicos.proto
│   │   └── tipos_comuns.proto
│   └── google/
│       └── protobuf/
│           └── timestamp.proto
├── build.rs
├── Cargo.toml
└── src/
    └── main.rs

2. Versione seus Protos

syntax = "proto3";

package meuapp.v1;  // Versionamento no package

// Nunca remova ou renumere campos!
// Use reserved para campos removidos
message Usuario {
  uint64 id = 1;
  string nome = 2;
  string email = 3;

  reserved 4;  // Campo 'idade' removido na v2
  reserved "idade";
}

3. Use Optional para Campos Explicitamente Opcionais

syntax = "proto3";

message Perfil {
  string nome = 1;           // Sempre presente (empty string se nao definido)
  optional string bio = 2;   // Explicitamente opcional (gera Option<String> em Rust)
  optional int32 idade = 3;  // Gera Option<i32>
}

4. Prefira Mensagens Wrapper para Tipos Complexos

// Em vez de campos soltos, agrupe em mensagens
// Isso facilita extensao futura e reutilizacao

// BOM: mensagem wrapper
// message Pagina {
//   int32 numero = 1;
//   int32 tamanho = 2;
// }

// RUIM: campos soltos
// message ListarRequest {
//   int32 pagina_numero = 1;
//   int32 pagina_tamanho = 2;
//   int32 outra_pagina_numero = 3;  // Confuso
// }

5. Trate Valores Default do Proto3

use meuapp::Usuario;

fn processar_usuario(usuario: &Usuario) {
    // Em proto3, campos nao definidos tem valores default:
    // string -> ""
    // int -> 0
    // bool -> false
    // enum -> primeiro valor (0)
    // message -> None

    // Verifique campos obrigatorios
    if usuario.nome.is_empty() {
        eprintln!("Aviso: usuario sem nome");
    }

    // Use match para enums
    match meuapp::Status::try_from(usuario.status) {
        Ok(status) => println!("Status: {:?}", status),
        Err(_) => eprintln!("Status desconhecido: {}", usuario.status),
    }

    // Mensagens aninhadas sao Option
    if let Some(endereco) = &usuario.endereco {
        println!("Cidade: {}", endereco.cidade);
    }
}

Exemplos Práticos

Exemplo Completo: Sistema de Mensagens com Serialização

use prost::Message;
use std::collections::HashMap;
use std::io::{Read, Write};
use std::time::{SystemTime, UNIX_EPOCH};

pub mod meuapp {
    include!(concat!(env!("OUT_DIR"), "/meuapp.rs"));
}

use meuapp::*;

// === Repositorio de Mensagens ===

struct RepositorioMensagens {
    arquivo: String,
    mensagens: Vec<Notificacao>,
}

impl RepositorioMensagens {
    fn novo(arquivo: &str) -> Self {
        RepositorioMensagens {
            arquivo: arquivo.to_string(),
            mensagens: Vec::new(),
        }
    }

    fn adicionar(&mut self, notif: Notificacao) {
        self.mensagens.push(notif);
    }

    fn salvar(&self) -> Result<(), Box<dyn std::error::Error>> {
        let mut arquivo = std::fs::File::create(&self.arquivo)?;

        for msg in &self.mensagens {
            let mut buf = Vec::new();
            msg.encode(&mut buf)?;

            // Prefixar com tamanho (length-delimited)
            let tamanho = buf.len() as u32;
            arquivo.write_all(&tamanho.to_le_bytes())?;
            arquivo.write_all(&buf)?;
        }

        println!("Salvas {} mensagens em '{}'", self.mensagens.len(), self.arquivo);
        Ok(())
    }

    fn carregar(&mut self) -> Result<(), Box<dyn std::error::Error>> {
        let mut arquivo = std::fs::File::open(&self.arquivo)?;
        let mut buf_tamanho = [0u8; 4];
        self.mensagens.clear();

        loop {
            match arquivo.read_exact(&mut buf_tamanho) {
                Ok(()) => {
                    let tamanho = u32::from_le_bytes(buf_tamanho) as usize;
                    let mut buf = vec![0u8; tamanho];
                    arquivo.read_exact(&mut buf)?;
                    let msg = Notificacao::decode(buf.as_slice())?;
                    self.mensagens.push(msg);
                }
                Err(ref e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break,
                Err(e) => return Err(e.into()),
            }
        }

        println!("Carregadas {} mensagens de '{}'", self.mensagens.len(), self.arquivo);
        Ok(())
    }

    fn listar(&self) {
        for msg in &self.mensagens {
            println!("ID: {}, Para: {}", msg.id, msg.destinatario);
            match &msg.conteudo {
                Some(notificacao::Conteudo::Texto(t)) => println!("  Texto: {}", t),
                Some(notificacao::Conteudo::Email(e)) => println!("  Email: {}", e.assunto),
                Some(notificacao::Conteudo::Push(p)) => println!("  Push: {}", p.titulo),
                None => println!("  (sem conteudo)"),
            }
        }
    }

    fn estatisticas(&self) -> HashMap<String, usize> {
        let mut stats = HashMap::new();

        for msg in &self.mensagens {
            let tipo = match &msg.conteudo {
                Some(notificacao::Conteudo::Texto(_)) => "texto",
                Some(notificacao::Conteudo::Email(_)) => "email",
                Some(notificacao::Conteudo::Push(_)) => "push",
                None => "vazio",
            };
            *stats.entry(tipo.to_string()).or_insert(0) += 1;
        }

        stats
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut repo = RepositorioMensagens::novo("/tmp/mensagens.pb");

    // Criar mensagens
    repo.adicionar(Notificacao {
        id: 1,
        destinatario: "maria@exemplo.com".to_string(),
        conteudo: Some(notificacao::Conteudo::Email(EmailNotificacao {
            assunto: "Bem-vinda ao sistema!".to_string(),
            corpo_html: "<h1>Ola Maria!</h1>".to_string(),
            anexos: vec![],
        })),
    });

    repo.adicionar(Notificacao {
        id: 2,
        destinatario: "joao@exemplo.com".to_string(),
        conteudo: Some(notificacao::Conteudo::Texto(
            "Seu pedido foi enviado!".to_string(),
        )),
    });

    repo.adicionar(Notificacao {
        id: 3,
        destinatario: "device_token_xyz".to_string(),
        conteudo: Some(notificacao::Conteudo::Push(PushNotificacao {
            titulo: "Promocao!".to_string(),
            corpo: "50% de desconto hoje!".to_string(),
            icone: "sale.png".to_string(),
        })),
    });

    // Salvar e carregar
    repo.salvar()?;

    let mut repo2 = RepositorioMensagens::novo("/tmp/mensagens.pb");
    repo2.carregar()?;

    println!("\n=== Mensagens ===");
    repo2.listar();

    println!("\n=== Estatisticas ===");
    for (tipo, contagem) in repo2.estatisticas() {
        println!("  {}: {}", tipo, contagem);
    }

    // Comparar tamanho com JSON
    let json_size: usize = repo.mensagens.iter().map(|m| {
        let mut buf = Vec::new();
        m.encode(&mut buf).unwrap();
        buf.len()
    }).sum();

    println!("\n=== Tamanho Total ===");
    println!("  Protobuf: {} bytes", json_size);

    // Cleanup
    let _ = std::fs::remove_file("/tmp/mensagens.pb");

    Ok(())
}

Comparação com Alternativas

FormatoTamanhoVelocidadeSchemaLegibilidade
Protobuf (prost)Muito pequenoMuito rapido.proto (obrigatorio)Binario
JSON (serde_json)GrandeRapidoOpcionalTexto legivel
MessagePack (rmp)PequenoMuito rapidoNaoBinario
Cap’n ProtoMuito pequenoExtremamente rapido.capnpBinario
FlatBuffersMuito pequenoExtremamente rapido.fbsBinario
BincodePequenoMuito rapidoNaoBinario
CBORPequenoRapidoNaoBinario

Prost/Protobuf e ideal quando voce precisa de interoperabilidade entre linguagens, schema evolutivo, e integracao com gRPC. Para comunicacao Rust-Rust pura, bincode pode ser mais simples. Para APIs HTTP, JSON continua sendo o padrao.

Conclusão

O prost torna Protocol Buffers uma escolha natural para projetos Rust que precisam de serializacao binaria eficiente e interoperabilidade entre linguagens. A geracao de codigo idiomatico, combinada com a integracao transparente com Tonic para gRPC, cria um pipeline completo para comunicacao entre microsservicos.

Lembre-se de nunca remover ou renumerar campos em seus protos (use reserved), versionar seus packages, e aproveitar os tipos well-known do prost-types para timestamps e duracoes.

Proximos passos:

  • Explore tonic para criar servicos gRPC completos
  • Veja serde para comparar com serializacao JSON
  • Confira tokio para o runtime async necessario para gRPC