YAML é um formato popular para arquivos de configuração, especialmente em ferramentas DevOps como Docker Compose, Kubernetes e CI/CD pipelines. A crate serde_yaml integra com o ecossistema Serde, permitindo deserializar YAML diretamente em structs tipadas. Nesta receita, você vai aprender a ler, escrever e manipular YAML em Rust.
Dependências
[package]
name = "receita-yaml"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_yaml = "0.9"
Código Completo
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
// =============================================
// 1. Definir structs de configuração
// =============================================
#[derive(Debug, Serialize, Deserialize)]
struct Config {
app: AppConfig,
servidor: ServidorConfig,
banco: BancoConfig,
#[serde(default)]
servicos: Vec<Servico>,
#[serde(default)]
variaveis: HashMap<String, String>,
}
#[derive(Debug, Serialize, Deserialize)]
struct AppConfig {
nome: String,
versao: String,
ambiente: String,
#[serde(default)]
debug: bool,
}
#[derive(Debug, Serialize, Deserialize)]
struct ServidorConfig {
host: String,
porta: u16,
#[serde(default = "default_workers")]
workers: usize,
tls: Option<TlsConfig>,
}
#[derive(Debug, Serialize, Deserialize)]
struct TlsConfig {
cert: String,
key: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct BancoConfig {
driver: String,
host: String,
porta: u16,
nome: String,
usuario: String,
senha: String,
#[serde(default = "default_pool")]
pool_size: u32,
}
#[derive(Debug, Serialize, Deserialize)]
struct Servico {
nome: String,
url: String,
#[serde(default = "default_timeout")]
timeout_ms: u64,
}
fn default_workers() -> usize { 4 }
fn default_pool() -> u32 { 10 }
fn default_timeout() -> u64 { 5000 }
fn main() {
// =============================================
// 2. Parse de string YAML
// =============================================
println!("=== Parse de String YAML ===");
let yaml_str = r#"
app:
nome: "Minha API"
versao: "1.0.0"
ambiente: produção
debug: false
servidor:
host: "0.0.0.0"
porta: 8080
workers: 8
tls:
cert: /etc/ssl/cert.pem
key: /etc/ssl/key.pem
banco:
driver: postgresql
host: db.exemplo.com
porta: 5432
nome: minha_api
usuario: app_user
senha: s3cr3t
pool_size: 20
servicos:
- nome: auth
url: "http://auth-service:3001"
timeout_ms: 2000
- nome: email
url: "http://email-service:3002"
- nome: cache
url: "http://redis:6379"
timeout_ms: 1000
variaveis:
LOG_LEVEL: info
SENTRY_DSN: "https://abc@sentry.io/123"
FEATURE_FLAG: "true"
"#;
let config: Config = serde_yaml::from_str(yaml_str).unwrap();
println!("App: {} v{}", config.app.nome, config.app.versao);
println!("Ambiente: {}", config.app.ambiente);
println!("Servidor: {}:{}", config.servidor.host, config.servidor.porta);
println!("Workers: {}", config.servidor.workers);
println!("TLS: {:?}", config.servidor.tls.is_some());
println!("Banco: {}@{}:{}/{}",
config.banco.usuario, config.banco.host,
config.banco.porta, config.banco.nome
);
println!("\nServiços:");
for s in &config.servicos {
println!(" {} -> {} (timeout: {}ms)", s.nome, s.url, s.timeout_ms);
}
println!("\nVariáveis:");
for (k, v) in &config.variaveis {
println!(" {} = {}", k, v);
}
// =============================================
// 3. Parse com campos opcionais ausentes
// =============================================
println!("\n=== Config Mínima ===");
let yaml_minimal = r#"
app:
nome: "App Simples"
versao: "0.1.0"
ambiente: desenvolvimento
servidor:
host: localhost
porta: 3000
banco:
driver: sqlite
host: ""
porta: 0
nome: dados.db
usuario: ""
senha: ""
"#;
let config: Config = serde_yaml::from_str(yaml_minimal).unwrap();
println!("Workers (padrão): {}", config.servidor.workers);
println!("Pool (padrão): {}", config.banco.pool_size);
println!("TLS: {:?}", config.servidor.tls);
println!("Serviços: {} (vazio)", config.servicos.len());
// =============================================
// 4. Serializar struct para YAML
// =============================================
println!("\n=== Serializar para YAML ===");
let config_nova = Config {
app: AppConfig {
nome: "Nova API".into(),
versao: "2.0.0".into(),
ambiente: "staging".into(),
debug: true,
},
servidor: ServidorConfig {
host: "0.0.0.0".into(),
porta: 9090,
workers: 2,
tls: None,
},
banco: BancoConfig {
driver: "postgresql".into(),
host: "localhost".into(),
porta: 5432,
nome: "staging_db".into(),
usuario: "admin".into(),
senha: "dev123".into(),
pool_size: 5,
},
servicos: vec![
Servico {
nome: "auth".into(),
url: "http://localhost:3001".into(),
timeout_ms: 3000,
},
],
variaveis: HashMap::from([
("LOG_LEVEL".into(), "debug".into()),
]),
};
let yaml_output = serde_yaml::to_string(&config_nova).unwrap();
println!("{}", yaml_output);
// =============================================
// 5. Escrever e ler arquivo YAML
// =============================================
println!("=== Arquivo YAML ===");
let caminho = "/tmp/config_teste.yaml";
// Escrever
fs::write(caminho, &yaml_output).unwrap();
println!("Salvo em: {}", caminho);
// Ler
let conteudo = fs::read_to_string(caminho).unwrap();
let config_lida: Config = serde_yaml::from_str(&conteudo).unwrap();
println!("Lido: {} v{}", config_lida.app.nome, config_lida.app.versao);
// Limpar
let _ = fs::remove_file(caminho);
// =============================================
// 6. Parse genérico com serde_yaml::Value
// =============================================
println!("\n=== Parse Genérico ===");
let yaml = r#"
nome: exemplo
lista:
- um
- dois
- três
aninhado:
chave: valor
numero: 42
"#;
let valor: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
// Acessar valores
println!("Nome: {:?}", valor["nome"].as_str());
println!("Lista[0]: {:?}", valor["lista"][0].as_str());
println!("Aninhado.chave: {:?}", valor["aninhado"]["chave"].as_str());
println!("Aninhado.numero: {:?}", valor["aninhado"]["numero"].as_u64());
// =============================================
// 7. Tratamento de erros
// =============================================
println!("\n=== Tratamento de Erros ===");
let yaml_invalido = r#"
app:
nome: "Teste"
versao: 123 # esperava string, recebeu número
ambiente: dev
"#;
match serde_yaml::from_str::<Config>(yaml_invalido) {
Ok(c) => println!("Config: {:?}", c),
Err(e) => println!("Erro de parse:\n {}", e),
}
// YAML com indentação errada
let yaml_quebrado = "chave:\n sub: valor\n sub2: erro";
match serde_yaml::from_str::<serde_yaml::Value>(yaml_quebrado) {
Ok(v) => println!("Valor: {:?}", v),
Err(e) => println!("Erro de sintaxe:\n {}", e),
}
// =============================================
// 8. Converter entre YAML e JSON
// =============================================
println!("\n=== YAML -> Value -> Display ===");
let yaml = "nome: Rust\nversao: 2026";
let valor: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
let yaml_bonito = serde_yaml::to_string(&valor).unwrap();
println!("{}", yaml_bonito);
}
Saída do Programa
=== Parse de String YAML ===
App: Minha API v1.0.0
Ambiente: produção
Servidor: 0.0.0.0:8080
Workers: 8
TLS: true
Banco: app_user@db.exemplo.com:5432/minha_api
Serviços:
auth -> http://auth-service:3001 (timeout: 2000ms)
email -> http://email-service:3002 (timeout: 5000ms)
cache -> http://redis:6379 (timeout: 1000ms)
Variáveis:
LOG_LEVEL = info
SENTRY_DSN = https://abc@sentry.io/123
FEATURE_FLAG = true
=== Config Mínima ===
Workers (padrão): 4
Pool (padrão): 10
TLS: None
Serviços: 0 (vazio)
=== Serializar para YAML ===
app:
nome: Nova API
versao: 2.0.0
ambiente: staging
debug: true
...
=== Parse Genérico ===
Nome: Some("exemplo")
Lista[0]: Some("um")
Aninhado.chave: Some("valor")
Aninhado.numero: Some(42)
=== Tratamento de Erros ===
Erro de parse:
servidor: missing field `servidor` at line 1 column 1
TOML vs YAML
| Aspecto | TOML | YAML |
|---|---|---|
| Aninhamento | Seções com [tabela] | Indentação |
| Arrays | [1, 2, 3] ou [[tabela]] | - item por linha |
| Tipagem | Rigorosa | Implícita (strings sem aspas) |
| Complexidade | Simples e previsível | Flexível mas com pegadinhas |
| Uso no Rust | Cargo.toml, configs simples | K8s, Docker Compose, CI/CD |
Recomendação: Use TOML para configurações de aplicação Rust. Use YAML quando precisar interagir com ferramentas DevOps ou dados com aninhamento profundo.
Veja Também
- Ler Arquivo TOML em Rust — compare com a abordagem TOML, o formato nativo do Rust
- Ler Variáveis de Ambiente em Rust — combine config YAML com variáveis de ambiente
- Tratar Erros em Rust — trate erros de parse de YAML de forma robusta
- Tutorial: CLI com Clap — combine config files com argumentos CLI
- Glossário: Crate — entenda como adicionar dependências ao projeto
- YAML em Go — compare com yaml.v3 em Go