Serde: O Framework de Serialização do Rust

Guia completo do Serde para serialização e desserialização em Rust. Aprenda sobre derive macros, JSON, TOML, YAML, atributos customizados, zero-copy e exemplos práticos.

O Serde é o framework de serialização e desserialização mais utilizado em Rust, e por boas razões: ele combina ergonomia excepcional com performance de referência mundial. O nome vem de serialize/deserialize, e sua arquitetura baseada em traits permite suportar dezenas de formatos de dados — JSON, TOML, YAML, MessagePack, CBOR, Bincode e muitos outros — tudo com a mesma interface.

O que torna o Serde especial é seu uso extensivo de macros procedurais derive que geram código de serialização em tempo de compilação, resultando em zero overhead em runtime. Não há reflection, não há alocações extras — apenas código otimizado gerado pelo compilador.

Praticamente todo projeto Rust não-trivial utiliza o Serde. Ele é essencial para APIs web, arquivos de configuração, comunicação entre serviços, armazenamento de dados e qualquer cenário que envolva converter estruturas Rust para/de formatos externos.

Instalação

Adicione o Serde e os formatos desejados ao Cargo.toml:

[dependencies]
# Serde core com derive macros
serde = { version = "1.0", features = ["derive"] }

# Formatos de dados (adicione os que precisar)
serde_json = "1.0"          # JSON
toml = "0.8"                 # TOML
serde_yaml = "0.9"          # YAML
serde_cbor = "0.11"         # CBOR (binário compacto)
bincode = "1.3"             # Bincode (binário eficiente)
rmp-serde = "1.1"           # MessagePack
csv = "1.3"                  # CSV

Uso Básico

Derive Macros: Serialize e Deserialize

use serde::{Deserialize, Serialize};

// Derive automático para serializar e desserializar
#[derive(Debug, Serialize, Deserialize)]
struct Usuario {
    nome: String,
    email: String,
    idade: u32,
    ativo: bool,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Criar um usuário
    let usuario = Usuario {
        nome: "Maria Silva".to_string(),
        email: "maria@exemplo.com".to_string(),
        idade: 28,
        ativo: true,
    };

    // Serializar para JSON
    let json = serde_json::to_string_pretty(&usuario)?;
    println!("JSON:\n{}", json);
    // {
    //   "nome": "Maria Silva",
    //   "email": "maria@exemplo.com",
    //   "idade": 28,
    //   "ativo": true
    // }

    // Desserializar de JSON
    let json_str = r#"{"nome":"João","email":"joao@exemplo.com","idade":35,"ativo":false}"#;
    let usuario2: Usuario = serde_json::from_str(json_str)?;
    println!("Desserializado: {:?}", usuario2);

    Ok(())
}

Trabalhando com Diferentes Formatos

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Config {
    app_nome: String,
    porta: u16,
    debug: bool,
    tags: Vec<String>,
}

fn demonstrar_formatos() -> Result<(), Box<dyn std::error::Error>> {
    let config = Config {
        app_nome: "MeuApp".to_string(),
        porta: 8080,
        debug: true,
        tags: vec!["web".to_string(), "api".to_string()],
    };

    // JSON
    let json = serde_json::to_string_pretty(&config)?;
    println!("=== JSON ===\n{}\n", json);

    // TOML
    let toml_str = toml::to_string_pretty(&config)?;
    println!("=== TOML ===\n{}\n", toml_str);

    // YAML
    let yaml = serde_yaml::to_string(&config)?;
    println!("=== YAML ===\n{}\n", yaml);

    // Bincode (binário compacto)
    let bytes = bincode::serialize(&config)?;
    println!("=== Bincode ===\n{} bytes\n", bytes.len());

    // Desserializar de qualquer formato
    let de_json: Config = serde_json::from_str(&json)?;
    let de_toml: Config = toml::from_str(&toml_str)?;
    let de_yaml: Config = serde_yaml::from_str(&yaml)?;
    let de_bincode: Config = bincode::deserialize(&bytes)?;

    println!("Todos desserializados com sucesso!");

    Ok(())
}

Enums com Serde

use serde::{Deserialize, Serialize};

// Enums são serializadas de forma flexível
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "tipo")]  // Tagged representation
enum Evento {
    #[serde(rename = "usuario_criado")]
    UsuarioCriado { nome: String, email: String },

    #[serde(rename = "pedido_realizado")]
    PedidoRealizado { pedido_id: u64, valor: f64 },

    #[serde(rename = "pagamento_confirmado")]
    PagamentoConfirmado { pedido_id: u64, metodo: String },
}

fn demonstrar_enums() -> Result<(), Box<dyn std::error::Error>> {
    let eventos = vec![
        Evento::UsuarioCriado {
            nome: "Ana".to_string(),
            email: "ana@exemplo.com".to_string(),
        },
        Evento::PedidoRealizado {
            pedido_id: 42,
            valor: 199.90,
        },
        Evento::PagamentoConfirmado {
            pedido_id: 42,
            metodo: "pix".to_string(),
        },
    ];

    for evento in &eventos {
        let json = serde_json::to_string_pretty(evento)?;
        println!("{}\n", json);
    }
    // {"tipo":"usuario_criado","nome":"Ana","email":"ana@exemplo.com"}
    // {"tipo":"pedido_realizado","pedido_id":42,"valor":199.9}
    // {"tipo":"pagamento_confirmado","pedido_id":42,"metodo":"pix"}

    // Desserializar automaticamente escolhe a variante correta
    let json = r#"{"tipo":"pedido_realizado","pedido_id":99,"valor":49.90}"#;
    let evento: Evento = serde_json::from_str(json)?;
    println!("Desserializado: {:?}", evento);

    Ok(())
}

Representações de Enum

use serde::{Deserialize, Serialize};

// Externally tagged (padrão)
// {"UsuarioCriado": {"nome": "Ana"}}
#[derive(Serialize, Deserialize)]
enum EventoExterno {
    UsuarioCriado { nome: String },
    PedidoRealizado { id: u64 },
}

// Internally tagged
// {"tipo": "usuario_criado", "nome": "Ana"}
#[derive(Serialize, Deserialize)]
#[serde(tag = "tipo")]
enum EventoInterno {
    #[serde(rename = "usuario_criado")]
    UsuarioCriado { nome: String },
    #[serde(rename = "pedido_realizado")]
    PedidoRealizado { id: u64 },
}

// Adjacently tagged
// {"tipo": "usuario_criado", "dados": {"nome": "Ana"}}
#[derive(Serialize, Deserialize)]
#[serde(tag = "tipo", content = "dados")]
enum EventoAdjacente {
    #[serde(rename = "usuario_criado")]
    UsuarioCriado { nome: String },
    #[serde(rename = "pedido_realizado")]
    PedidoRealizado { id: u64 },
}

// Untagged
// {"nome": "Ana"} ou {"id": 42}
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum EventoSemTag {
    UsuarioCriado { nome: String },
    PedidoRealizado { id: u64 },
}

Recursos Avançados

Atributos #[serde(…)]

Atributos de Campo

use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};

#[derive(Debug, Serialize, Deserialize)]
struct Produto {
    // Renomear campo no JSON
    #[serde(rename = "product_name")]
    nome: String,

    // Campo opcional (usa None se ausente)
    #[serde(skip_serializing_if = "Option::is_none")]
    descricao: Option<String>,

    // Valor padrão se ausente
    #[serde(default)]
    quantidade: u32,

    // Valor padrão customizado
    #[serde(default = "preco_padrao")]
    preco: f64,

    // Ignorar campo na serialização
    #[serde(skip)]
    cache_interno: String,

    // Apenas serializar (não desserializar)
    #[serde(skip_deserializing)]
    id_gerado: String,

    // Alias para desserialização (aceita múltiplos nomes)
    #[serde(alias = "created_at", alias = "criado_em")]
    data_criacao: String,

    // Serializar/desserializar com função customizada
    #[serde(serialize_with = "serialize_tags", deserialize_with = "deserialize_tags")]
    tags: Vec<String>,

    // Flatten: incorpora campos de outro struct
    #[serde(flatten)]
    metadata: Metadata,
}

fn preco_padrao() -> f64 {
    0.0
}

#[derive(Debug, Serialize, Deserialize)]
struct Metadata {
    versao: String,
    autor: String,
}

// Funções customizadas de serialização
fn serialize_tags<S>(tags: &[String], serializer: S) -> Result<S::Ok, S::Error>
where
    S: serde::Serializer,
{
    // Serializar como string separada por vírgulas
    serializer.serialize_str(&tags.join(","))
}

fn deserialize_tags<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let s = String::deserialize(deserializer)?;
    Ok(s.split(',').map(|t| t.trim().to_string()).collect())
}

Atributos de Container

use serde::{Deserialize, Serialize};

// Renomear todos os campos automaticamente
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ConfigApi {
    nome_do_app: String,        // -> "nomeDoApp"
    porta_http: u16,            // -> "portaHttp"
    max_conexoes: usize,        // -> "maxConexoes"
}

// Outras opções de rename_all:
// "lowercase", "UPPERCASE", "camelCase", "PascalCase",
// "snake_case", "SCREAMING_SNAKE_CASE", "kebab-case",
// "SCREAMING-KEBAB-CASE"

// Negar campos desconhecidos (erro se JSON tiver campos extras)
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct ConfigEstrita {
    nome: String,
    porta: u16,
}

// Verificação customizada após desserialização
#[derive(Serialize, Deserialize)]
#[serde(try_from = "ConfigRaw")]
struct ConfigValidada {
    nome: String,
    porta: u16,
}

#[derive(Deserialize)]
struct ConfigRaw {
    nome: String,
    porta: u16,
}

impl TryFrom<ConfigRaw> for ConfigValidada {
    type Error = String;

    fn try_from(raw: ConfigRaw) -> Result<Self, String> {
        if raw.porta == 0 {
            return Err("Porta não pode ser zero".to_string());
        }
        if raw.nome.is_empty() {
            return Err("Nome não pode ser vazio".to_string());
        }
        Ok(ConfigValidada {
            nome: raw.nome,
            porta: raw.porta,
        })
    }
}

Desserialização com serde_json::Value

use serde_json::{json, Value};

fn trabalhar_com_json_dinamico() -> Result<(), Box<dyn std::error::Error>> {
    // Construir JSON programaticamente
    let dados = json!({
        "nome": "Produto X",
        "preco": 29.90,
        "tags": ["eletrônico", "novo"],
        "dimensoes": {
            "largura": 10.0,
            "altura": 20.0,
            "profundidade": 5.0
        }
    });

    // Acessar campos dinamicamente
    println!("Nome: {}", dados["nome"]);
    println!("Primeira tag: {}", dados["tags"][0]);
    println!("Largura: {}", dados["dimensoes"]["largura"]);

    // Verificar tipo
    if dados["preco"].is_f64() {
        let preco = dados["preco"].as_f64().unwrap();
        println!("Preço: R$ {:.2}", preco);
    }

    // Converter Value para tipo concreto
    #[derive(serde::Deserialize, Debug)]
    struct Dimensoes {
        largura: f64,
        altura: f64,
        profundidade: f64,
    }

    let dim: Dimensoes = serde_json::from_value(dados["dimensoes"].clone())?;
    println!("Dimensões: {:?}", dim);

    // Misturar tipado e dinâmico
    #[derive(serde::Deserialize, Debug)]
    struct ProdutoParcial {
        nome: String,
        preco: f64,
        #[serde(flatten)]
        extras: serde_json::Map<String, Value>,
    }

    let produto: ProdutoParcial = serde_json::from_value(dados)?;
    println!("Extras: {:?}", produto.extras);

    Ok(())
}

Zero-Copy Deserialization

use serde::Deserialize;

// Zero-copy: empresta dados do buffer original em vez de alocar
#[derive(Debug, Deserialize)]
struct LogEntry<'a> {
    #[serde(borrow)]
    timestamp: &'a str,    // &str em vez de String - sem alocação
    #[serde(borrow)]
    nivel: &'a str,
    #[serde(borrow)]
    mensagem: &'a str,
    codigo: u32,           // tipos Copy são sempre eficientes
}

fn demonstrar_zero_copy() -> Result<(), Box<dyn std::error::Error>> {
    let json_data = r#"{
        "timestamp": "2024-03-15T10:30:00Z",
        "nivel": "INFO",
        "mensagem": "Servidor iniciado na porta 8080",
        "codigo": 200
    }"#;

    // Desserializar emprestando do json_data original
    let entry: LogEntry = serde_json::from_str(json_data)?;
    println!("{}: [{}] {} ({})", entry.timestamp, entry.nivel, entry.mensagem, entry.codigo);

    // Processar milhares de entradas sem alocar strings
    let logs = vec![json_data; 1000];
    let mut total_codigos: u32 = 0;
    for log in &logs {
        let entry: LogEntry = serde_json::from_str(log)?;
        total_codigos += entry.codigo;
    }
    println!("Soma dos códigos: {}", total_codigos);

    Ok(())
}

Serialização Customizada Completa

use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;

// Tipo que precisa de serialização customizada
#[derive(Debug, Clone)]
struct Cor {
    r: u8,
    g: u8,
    b: u8,
}

// Serializar como string "#RRGGBB"
impl Serialize for Cor {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let hex = format!("#{:02X}{:02X}{:02X}", self.r, self.g, self.b);
        serializer.serialize_str(&hex)
    }
}

// Desserializar de string "#RRGGBB"
impl<'de> Deserialize<'de> for Cor {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        let s = s.trim_start_matches('#');

        if s.len() != 6 {
            return Err(serde::de::Error::custom("cor deve ter formato #RRGGBB"));
        }

        let r = u8::from_str_radix(&s[0..2], 16)
            .map_err(|_| serde::de::Error::custom("valor R inválido"))?;
        let g = u8::from_str_radix(&s[2..4], 16)
            .map_err(|_| serde::de::Error::custom("valor G inválido"))?;
        let b = u8::from_str_radix(&s[4..6], 16)
            .map_err(|_| serde::de::Error::custom("valor B inválido"))?;

        Ok(Cor { r, g, b })
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct Tema {
    nome: String,
    cor_primaria: Cor,
    cor_secundaria: Cor,
    cor_fundo: Cor,
}

fn demonstrar_cor_customizada() -> Result<(), Box<dyn std::error::Error>> {
    let tema = Tema {
        nome: "Escuro".to_string(),
        cor_primaria: Cor { r: 66, g: 133, b: 244 },
        cor_secundaria: Cor { r: 52, g: 168, b: 83 },
        cor_fundo: Cor { r: 30, g: 30, b: 30 },
    };

    let json = serde_json::to_string_pretty(&tema)?;
    println!("{}", json);
    // {
    //   "nome": "Escuro",
    //   "cor_primaria": "#4285F4",
    //   "cor_secundaria": "#34A853",
    //   "cor_fundo": "#1E1E1E"
    // }

    let de_volta: Tema = serde_json::from_str(&json)?;
    println!("Cor primária: {:?}", de_volta.cor_primaria);

    Ok(())
}

Boas Práticas

1. Sempre Use derive Quando Possível

// Bom: derive automático
#[derive(Serialize, Deserialize)]
struct Config {
    nome: String,
    porta: u16,
}

// Evite: implementação manual desnecessária
// Só implemente manualmente quando realmente precisar de lógica customizada

2. Use rename_all para APIs Externas

// API JavaScript/JSON tipicamente usa camelCase
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RespostaApi {
    nome_completo: String,       // -> "nomeCompleto"
    data_nascimento: String,     // -> "dataNascimento"
    endereco_email: String,      // -> "enderecoEmail"
}

3. Skip Fields Desnecessários

#[derive(Serialize, Deserialize)]
struct Usuario {
    nome: String,
    email: String,

    // Não serializar campos temporários ou internos
    #[serde(skip)]
    _cache: Option<String>,

    // Não serializar None
    #[serde(skip_serializing_if = "Option::is_none")]
    telefone: Option<String>,

    // Não serializar coleções vazias
    #[serde(skip_serializing_if = "Vec::is_empty", default)]
    tags: Vec<String>,
}

4. Valide na Desserialização

#[derive(Deserialize)]
#[serde(try_from = "ConfigRaw")]
struct Config {
    porta: u16,
    max_conexoes: usize,
}

#[derive(Deserialize)]
struct ConfigRaw {
    porta: u16,
    max_conexoes: usize,
}

impl TryFrom<ConfigRaw> for Config {
    type Error = String;

    fn try_from(raw: ConfigRaw) -> Result<Self, String> {
        if raw.porta < 1024 {
            return Err(format!("Porta {} é reservada (< 1024)", raw.porta));
        }
        if raw.max_conexoes == 0 {
            return Err("max_conexoes deve ser > 0".to_string());
        }
        Ok(Config {
            porta: raw.porta,
            max_conexoes: raw.max_conexoes,
        })
    }
}

5. Prefira Tipos Fortes a serde_json::Value

// Evite: Value genérico perde type safety
fn processar_ruim(dados: serde_json::Value) {
    // Runtime panic se o campo não existir ou tiver tipo errado
    let nome = dados["nome"].as_str().unwrap();
}

// Bom: tipo concreto com validação em compile-time
#[derive(Deserialize)]
struct Dados {
    nome: String,
}

fn processar_bom(dados: Dados) {
    // Compilador garante que nome existe e é String
    println!("{}", dados.nome);
}

Exemplos Práticos

Exemplo 1: Parsing de Arquivo de Configuração

use serde::Deserialize;
use std::fs;
use std::path::Path;

#[derive(Debug, Deserialize)]
struct AppConfig {
    #[serde(default = "default_app")]
    app: AppSection,
    #[serde(default)]
    banco_dados: DatabaseSection,
    #[serde(default)]
    logging: LoggingSection,
}

#[derive(Debug, Deserialize)]
struct AppSection {
    nome: String,
    #[serde(default = "default_porta")]
    porta: u16,
    #[serde(default)]
    debug: bool,
    #[serde(default)]
    hosts_permitidos: Vec<String>,
}

#[derive(Debug, Deserialize, Default)]
struct DatabaseSection {
    #[serde(default = "default_db_url")]
    url: String,
    #[serde(default = "default_pool_size")]
    pool_size: u32,
    #[serde(default)]
    ssl: bool,
}

#[derive(Debug, Deserialize, Default)]
struct LoggingSection {
    #[serde(default = "default_log_level")]
    nivel: String,
    #[serde(default)]
    arquivo: Option<String>,
    #[serde(default)]
    json: bool,
}

fn default_app() -> AppSection {
    AppSection {
        nome: "MeuApp".to_string(),
        porta: 8080,
        debug: false,
        hosts_permitidos: vec![],
    }
}

fn default_porta() -> u16 { 8080 }
fn default_db_url() -> String { "postgres://localhost/mydb".to_string() }
fn default_pool_size() -> u32 { 10 }
fn default_log_level() -> String { "info".to_string() }

fn carregar_config(caminho: &str) -> Result<AppConfig, Box<dyn std::error::Error>> {
    let conteudo = fs::read_to_string(caminho)?;

    // Detectar formato pelo extensão
    let config: AppConfig = if caminho.ends_with(".toml") {
        toml::from_str(&conteudo)?
    } else if caminho.ends_with(".yaml") || caminho.ends_with(".yml") {
        serde_yaml::from_str(&conteudo)?
    } else if caminho.ends_with(".json") {
        serde_json::from_str(&conteudo)?
    } else {
        return Err("Formato não suportado. Use .toml, .yaml ou .json".into());
    };

    Ok(config)
}

Exemplo 2: Manipulação de Resposta de API

use serde::{Deserialize, Serialize};

// Resposta paginada genérica
#[derive(Debug, Deserialize)]
struct RespostaPaginada<T> {
    dados: Vec<T>,
    paginacao: Paginacao,
}

#[derive(Debug, Deserialize)]
struct Paginacao {
    pagina_atual: u32,
    total_paginas: u32,
    total_itens: u64,
    itens_por_pagina: u32,
}

// Modelo de usuário da API
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct UsuarioApi {
    id: u64,
    nome_completo: String,
    email: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    avatar_url: Option<String>,
    #[serde(rename = "isActive")]
    ativo: bool,
    #[serde(default)]
    permissoes: Vec<String>,
    #[serde(with = "timestamp_format")]
    criado_em: chrono::DateTime<chrono::Utc>,
}

// Módulo para formato de timestamp customizado
mod timestamp_format {
    use chrono::{DateTime, Utc, TimeZone};
    use serde::{self, Deserialize, Deserializer, Serializer};

    const FORMAT: &str = "%Y-%m-%dT%H:%M:%S%.3fZ";

    pub fn serialize<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = date.format(FORMAT).to_string();
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        DateTime::parse_from_rfc3339(&s)
            .map(|dt| dt.with_timezone(&Utc))
            .map_err(serde::de::Error::custom)
    }
}

// Exemplo de request body
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct CriarUsuarioRequest {
    nome_completo: String,
    email: String,
    senha: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    avatar_url: Option<String>,
}

fn demonstrar_api() -> Result<(), Box<dyn std::error::Error>> {
    // Simular resposta da API
    let json_resposta = r#"{
        "dados": [
            {
                "id": 1,
                "nomeCompleto": "Maria Silva",
                "email": "maria@exemplo.com",
                "isActive": true,
                "permissoes": ["admin", "editor"],
                "criadoEm": "2024-01-15T10:30:00.000Z"
            }
        ],
        "paginacao": {
            "paginaAtual": 1,
            "totalPaginas": 5,
            "totalItens": 47,
            "itensPorPagina": 10
        }
    }"#;

    let resposta: RespostaPaginada<UsuarioApi> = serde_json::from_str(json_resposta)?;
    println!("Total de usuários: {}", resposta.paginacao.total_itens);
    for usuario in &resposta.dados {
        println!("- {} ({})", usuario.nome_completo, usuario.email);
    }

    // Criar request body
    let novo_usuario = CriarUsuarioRequest {
        nome_completo: "João Santos".to_string(),
        email: "joao@exemplo.com".to_string(),
        senha: "senha_segura_123".to_string(),
        avatar_url: None,
    };

    let body = serde_json::to_string(&novo_usuario)?;
    println!("Request body: {}", body);

    Ok(())
}

Exemplo 3: Streaming JSON de Arquivos Grandes

use serde::Deserialize;
use std::fs::File;
use std::io::BufReader;

#[derive(Debug, Deserialize)]
struct LogLine {
    timestamp: String,
    level: String,
    message: String,
    #[serde(default)]
    fields: serde_json::Map<String, serde_json::Value>,
}

fn processar_logs_grandes(caminho: &str) -> Result<(), Box<dyn std::error::Error>> {
    let file = File::open(caminho)?;
    let reader = BufReader::new(file);

    // Usar Deserializer de streaming para processar linha por linha
    let stream = serde_json::Deserializer::from_reader(reader).into_iter::<LogLine>();

    let mut total = 0u64;
    let mut erros = 0u64;

    for resultado in stream {
        match resultado {
            Ok(log) => {
                total += 1;
                if log.level == "ERROR" {
                    erros += 1;
                    eprintln!("[ERRO] {}: {}", log.timestamp, log.message);
                }
            }
            Err(e) => {
                eprintln!("Erro ao parsear linha: {}", e);
            }
        }
    }

    println!("Processados {} logs, {} erros encontrados", total, erros);

    Ok(())
}

Comparação com Alternativas

CaracterísticaSerde (Rust)Jackson (Java)Newtonsoft (C#)encoding/json (Go)
PerformanceExcelenteBoaBoaBoa
Zero-copySimNãoNãoParcial
Derive macrosSimAnnotationsAttributesTags
Múltiplos formatos20+5+3+JSON apenas
TipagemForte (compile-time)Forte (runtime)Forte (runtime)Fraca
CustomizaçãoMuito flexívelFlexívelFlexívelLimitada
Overhead runtimeZeroReflexãoReflexãoReflexão
ValidaçãoVia try_fromAnnotationsAttributesManual
StreamingSimSimSimSim
Tamanho do ecossistemaEnormeEnormeGrandePequeno

O Serde se destaca por:

  • Zero overhead: código gerado em compile-time, sem reflection
  • Flexibilidade de formatos: o mesmo derive funciona para JSON, TOML, YAML, binário e dezenas de outros
  • Segurança de tipos: erros detectados em tempo de compilação
  • Zero-copy: possibilidade de emprestar dados sem alocação
  • Ecossistema vasto: virtualmente toda crate Rust integra com Serde

Conclusão

O Serde é a espinha dorsal da serialização em Rust, e dominá-lo é essencial para qualquer desenvolvedor na linguagem. Com suas derive macros poderosas, suporte a dezenas de formatos e performance excepcional, ele torna o trabalho com dados externos tão natural quanto trabalhar com tipos nativos Rust.

Pontos-chave para lembrar:

  • derive(Serialize, Deserialize) resolve a maioria dos casos
  • Atributos #[serde(...)] permitem customização fina
  • rename_all adapta automaticamente para convenções de outras linguagens
  • skip_serializing_if gera JSON mais limpo
  • Value permite trabalhar com dados dinâmicos quando necessário
  • Zero-copy com &str e #[serde(borrow)] para máxima performance

Para aprofundar, consulte a documentação oficial do Serde e o guia de atributos.

No próximo passo, explore o Tokio para programação assíncrona em Rust.