TOML (Tom’s Obvious, Minimal Language) é o formato de configuração padrão do ecossistema Rust — o próprio Cargo.toml usa este formato. Ele é mais legível que JSON e menos ambíguo que YAML, sendo ideal para arquivos de configuração. Nesta receita, você vai aprender a ler, escrever e manipular arquivos TOML em Rust.
Dependências
[package]
name = "receita-toml"
version = "0.1.0"
edition = "2021"
[dependencies]
toml = "0.8"
serde = { version = "1", features = ["derive"] }
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 {
titulo: String,
versao: String,
debug: Option<bool>,
servidor: Servidor,
banco: Banco,
#[serde(default)]
tags: Vec<String>,
#[serde(default)]
extras: HashMap<String, String>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Servidor {
host: String,
porta: u16,
#[serde(default = "default_timeout")]
timeout_ms: u64,
#[serde(default)]
cors_origens: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Banco {
url: String,
pool_size: Option<u32>,
#[serde(default = "default_pool_size")]
max_conexoes: u32,
}
fn default_timeout() -> u64 { 5000 }
fn default_pool_size() -> u32 { 10 }
fn main() {
// =============================================
// 2. Parse de string TOML
// =============================================
println!("=== Parse de String TOML ===");
let toml_str = r#"
titulo = "Minha Aplicação"
versao = "1.0.0"
debug = true
tags = ["web", "api", "rust"]
[servidor]
host = "0.0.0.0"
porta = 8080
timeout_ms = 3000
cors_origens = ["http://localhost:3000", "https://meusite.com"]
[banco]
url = "postgresql://localhost:5432/meudb"
pool_size = 20
max_conexoes = 50
[extras]
ambiente = "produção"
regiao = "sa-east-1"
"#;
let config: Config = toml::from_str(toml_str).unwrap();
println!("Título: {}", config.titulo);
println!("Versão: {}", config.versao);
println!("Debug: {:?}", config.debug);
println!("Servidor: {}:{}", config.servidor.host, config.servidor.porta);
println!("Timeout: {}ms", config.servidor.timeout_ms);
println!("Banco: {}", config.banco.url);
println!("Pool: {:?}", config.banco.pool_size);
println!("Tags: {:?}", config.tags);
println!("Extras: {:?}", config.extras);
// =============================================
// 3. Parse com valores padrão (campos ausentes)
// =============================================
println!("\n=== Valores Padrão ===");
let toml_minimal = r#"
titulo = "App Simples"
versao = "0.1.0"
[servidor]
host = "localhost"
porta = 3000
[banco]
url = "sqlite://dados.db"
"#;
let config: Config = toml::from_str(toml_minimal).unwrap();
println!("Timeout (padrão): {}ms", config.servidor.timeout_ms); // 5000
println!("Max conexões (padrão): {}", config.banco.max_conexoes); // 10
println!("Debug (ausente): {:?}", config.debug); // None
println!("Tags (padrão vazio): {:?}", config.tags); // []
println!("CORS (padrão vazio): {:?}", config.servidor.cors_origens);
// =============================================
// 4. Ler e escrever arquivo TOML
// =============================================
println!("\n=== Ler/Escrever Arquivo ===");
let caminho = "/tmp/config_teste.toml";
// Escrever
let config_nova = Config {
titulo: "Meu Projeto".into(),
versao: "2.0.0".into(),
debug: Some(false),
servidor: Servidor {
host: "0.0.0.0".into(),
porta: 9090,
timeout_ms: 10000,
cors_origens: vec!["*".into()],
},
banco: Banco {
url: "postgresql://prod:5432/app".into(),
pool_size: Some(30),
max_conexoes: 100,
},
tags: vec!["produção".into(), "v2".into()],
extras: HashMap::from([
("ambiente".into(), "prod".into()),
]),
};
let toml_output = toml::to_string_pretty(&config_nova).unwrap();
fs::write(caminho, &toml_output).unwrap();
println!("Arquivo salvo em: {}", caminho);
println!("Conteúdo:\n{}", toml_output);
// Ler de volta
let conteudo = fs::read_to_string(caminho).unwrap();
let config_lida: Config = toml::from_str(&conteudo).unwrap();
println!("Lido de volta: {} v{}", config_lida.titulo, config_lida.versao);
// Limpar
let _ = fs::remove_file(caminho);
// =============================================
// 5. Parse genérico (sem struct definida)
// =============================================
println!("\n=== Parse Genérico ===");
let toml_str = r#"
nome = "exemplo"
porta = 8080
ativo = true
[database]
host = "localhost"
nome = "mydb"
"#;
let valor: toml::Value = toml::from_str(toml_str).unwrap();
// Acessar valores com indexação
println!("Nome: {}", valor["nome"].as_str().unwrap());
println!("Porta: {}", valor["porta"].as_integer().unwrap());
println!("Ativo: {}", valor["ativo"].as_bool().unwrap());
println!("DB Host: {}", valor["database"]["host"].as_str().unwrap());
// Verificar se chave existe
if let Some(v) = valor.get("inexistente") {
println!("Encontrado: {}", v);
} else {
println!("Chave 'inexistente' não encontrada");
}
// =============================================
// 6. Tratamento de erros de parse
// =============================================
println!("\n=== Tratamento de Erros ===");
let toml_invalido = r#"
titulo = "Teste"
porta = "não é número" # campo esperava u16
"#;
#[derive(Debug, Deserialize)]
struct ConfigSimples {
titulo: String,
porta: u16,
}
match toml::from_str::<ConfigSimples>(toml_invalido) {
Ok(c) => println!("Config: {:?}", c),
Err(e) => println!("Erro de parse:\n {}", e),
}
// TOML com sintaxe inválida
let toml_quebrado = "titulo = sem aspas";
match toml::from_str::<toml::Value>(toml_quebrado) {
Ok(v) => println!("Valor: {:?}", v),
Err(e) => println!("Erro de sintaxe:\n {}", e),
}
// =============================================
// 7. Exemplo prático: config de aplicação
// =============================================
println!("\n=== Exemplo Prático ===");
fn carregar_config(caminho: &str) -> Result<Config, Box<dyn std::error::Error>> {
let conteudo = fs::read_to_string(caminho)?;
let config: Config = toml::from_str(&conteudo)?;
Ok(config)
}
match carregar_config("/tmp/config_app.toml") {
Ok(config) => println!("Config carregada: {}", config.titulo),
Err(e) => println!("Usando config padrão (erro: {})", e),
}
}
Saída do Programa
=== Parse de String TOML ===
Título: Minha Aplicação
Versão: 1.0.0
Debug: Some(true)
Servidor: 0.0.0.0:8080
Timeout: 3000ms
Banco: postgresql://localhost:5432/meudb
Pool: Some(20)
Tags: ["web", "api", "rust"]
Extras: {"ambiente": "produção", "regiao": "sa-east-1"}
=== Valores Padrão ===
Timeout (padrão): 5000ms
Max conexões (padrão): 10
Debug (ausente): None
Tags (padrão vazio): []
CORS (padrão vazio): []
=== Ler/Escrever Arquivo ===
Arquivo salvo em: /tmp/config_teste.toml
Conteúdo:
titulo = "Meu Projeto"
versao = "2.0.0"
debug = false
tags = ["produção", "v2"]
...
Lido de volta: Meu Projeto v2.0.0
=== Parse Genérico ===
Nome: exemplo
Porta: 8080
Ativo: true
DB Host: localhost
Chave 'inexistente' não encontrada
=== Tratamento de Erros ===
Erro de parse:
TOML parse error at line 3, column 9...
=== Exemplo Prático ===
Usando config padrão (erro: No such file or directory...)
Dicas
- Use
Option<T>para campos que podem estar ausentes no TOML. - Use
#[serde(default)]para fornecer valores padrão quando campos não existem. - Use
#[serde(default = "funcao")]para valores padrão calculados por uma função. toml::Valuepermite parse genérico quando você não conhece a estrutura de antemão.toml::to_string_pretty()gera TOML formatado e legível para escrita.
Veja Também
- Ler Arquivo YAML em Rust — compare com a abordagem YAML usando serde
- Ler Variáveis de Ambiente em Rust — combine config TOML com env vars
- Tratar Erros em Rust — trate erros de leitura e parse
- Tutorial: CLI com Clap — combine config TOML com argumentos de linha de comando
- Rust Cheatsheet — referência rápida do Cargo.toml e dependências
- Configuração em aplicações IA — padrões de configuração em projetos de inteligência artificial