Introdução ao Serde: O Padrão de Serialização em Rust
Serialização é o processo de converter estruturas de dados em memória para um formato transportável (JSON, YAML, binário, etc.), enquanto desserialização faz o inverso. Em Rust, Serde é a biblioteca padrão para essas operações, oferecendo uma abordagem baseada em traits que é segura em tempo de compilação e extremamente eficiente.
Serde funciona através de macros procedurais que geram automaticamente o código necessário para serializar e desserializar suas estruturas. Diferentemente de outras linguagens, Rust não usa reflexão em tempo de execução — tudo é resolvido na compilação, resultando em zero overhead. Este artigo cobrirá desde configuração até casos de uso avançados.
Configuração e Conceitos Fundamentais
Dependências Necessárias
Para começar, adicione ao seu Cargo.toml:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
O serde é a biblioteca principal, e serde_json fornece suporte para JSON. Outras opções incluem serde_yaml, ron (Rusty Object Notation) e bincode para serialização binária.
Traits Principais
Serde define dois traits centrais:
Serialize: implementado por tipos que podem ser convertidos para um formato serializadoDeserialize: implementado por tipos que podem ser construídos a partir de dados serializados
Na prática, você rara implementa esses traits manualmente. A macro #[derive(Serialize, Deserialize)] gera tudo automaticamente:
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Pessoa {
nome: String,
idade: u32,
email: String,
}
fn main() {
let pessoa = Pessoa {
nome: "Alice".to_string(),
idade: 30,
email: "alice@example.com".to_string(),
};
// Serialização para JSON
let json = serde_json::to_string(&pessoa).unwrap();
println!("JSON: {}", json);
// Desserialização de JSON
let nova_pessoa: Pessoa = serde_json::from_str(&json).unwrap();
println!("Restaurada: {:?}", nova_pessoa);
}
Este exemplo simples demonstra o fluxo completo: criar uma struct, serializá-la para JSON e recuperá-la intacta.
Customização Avançada com Atributos
Controlando Serialização com #[serde(rename)]
Frequentemente você precisa de nomes diferentes em JSON versus Rust (snake_case vs camelCase). Use o atributo rename:
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Usuario {
#[serde(rename = "first_name")]
nome_primeiro: String,
#[serde(rename = "last_name")]
nome_sobrenome: String,
#[serde(skip)] // Não serializa este campo
senha_hash: String,
}
fn main() {
let usuario = Usuario {
nome_primeiro: "João".to_string(),
nome_sobrenome: "Silva".to_string(),
senha_hash: "hashsecuro123".to_string(),
};
let json = serde_json::to_string(&usuario).unwrap();
println!("{}", json);
// Saída: {"first_name":"João","last_name":"Silva"}
}
Valores Padrão e Campos Opcionais
Para tornar campos opcionais na desserialização, combine Option<T> com #[serde(default)]:
#[derive(Serialize, Deserialize)]
struct Config {
host: String,
#[serde(default)]
porta: u16, // Se ausente no JSON, usa Default::default()
#[serde(default)]
debug: bool,
}
fn main() {
let json = r#"{"host": "localhost"}"#;
let config: Config = serde_json::from_str(json).unwrap();
println!("Host: {}, Porta: {}, Debug: {}",
config.host, config.porta, config.debug);
// Saída: Host: localhost, Porta: 0, Debug: false
}
Casos de Uso Prático: APIs e Persistência
Trabalhando com APIs REST
Na prática, você frequentemente deserializa respostas de APIs externas. Considere este exemplo com uma API fictícia:
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct PostAPI {
id: u32,
title: String,
body: String,
#[serde(rename = "user_id")]
usuario_id: u32,
}
#[derive(Serialize, Deserialize)]
struct NovoPost {
title: String,
body: String,
}
fn processar_resposta_api(json_resposta: &str) -> Result<PostAPI, serde_json::Error> {
serde_json::from_str(json_resposta)
}
fn preparar_requisicao(post: &NovoPost) -> String {
serde_json::to_string(&post).unwrap()
}
fn main() {
let resposta_api = r#"{"id":1,"title":"Hello","body":"World","user_id":42}"#;
match processar_resposta_api(resposta_api) {
Ok(post) => println!("Post recebido: {:?}", post),
Err(e) => println!("Erro ao desserializar: {}", e),
}
}
Salvando em Arquivo
Serializar para arquivo é essencial em aplicações reais:
use std::fs;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Dados {
chave: String,
valor: i32,
}
fn salvar_dados(dados: &Dados, caminho: &str) -> std::io::Result<()> {
let json = serde_json::to_string_pretty(dados).unwrap();
fs::write(caminho, json)?;
Ok(())
}
fn carregar_dados(caminho: &str) -> Result<Dados, Box<dyn std::error::Error>> {
let conteudo = fs::read_to_string(caminho)?;
Ok(serde_json::from_str(&conteudo)?)
}
fn main() {
let dados = Dados {
chave: "exemplo".to_string(),
valor: 100,
};
salvar_dados(&dados, "dados.json").unwrap();
let carregados = carregar_dados("dados.json").unwrap();
println!("{:?}", carregados);
}
Conclusão
Três pontos essenciais para dominar Serde em Rust:
-
As macros
derivegeram todo o código automaticamente — não implemente traits manualmente sem necessidade. Serde é seguro em tempo de compilação porque não usa reflexão, tudo é resolvido na compilação. -
Customize com atributos inteligentemente —
rename,skip,defaulteflattenresolvem 95% dos casos reais. Conhecer esses atributos economiza horas de manutenção de código. -
Sempre trate erros explicitamente — use
Result<T, E>ao desserializar dados não confiáveis (APIs, arquivos do usuário). Nunca useunwrap()em código de produção.