Configuração & Ambiente
Camadas de flags de CLI, variáveis de ambiente e arquivos de configuração com uma ordem de precedência clara.
Receita
use clap::Parser;
use figment::{providers::{Env, Format, Serialized, Toml}, Figment};
use serde::Deserialize;
#[derive(Parser)]
struct Cli {
#[arg(short, long)]
config: Option<std::path::PathBuf>,
#[arg(long)]
port: Option<u16>,
}
#[derive(Debug, Deserialize)]
struct Settings {
port: u16,
host: String,
}
fn load(cli: &Cli) -> anyhow::Result<Settings> {
let mut figment = Figment::new()
.merge(Serialized::defaults(Settings { port: 8080, host: "127.0.0.1".into() }))
.merge(Toml::file(cli.config.clone().unwrap_or_else(|| "config.toml".into())))
.merge(Env::prefixed("APP_").split("_"));
if let Some(port) = cli.port {
figment = figment.merge(Serialized::from(("port", port)));
}
Ok(figment.extract()?)
}Quando usar isso: Qualquer CLI implantada em múltiplos ambientes onde os operadores precisam de substituições sem editar arquivos.
Exemplo de Trabalho
use clap::Parser;
use serde::Deserialize;
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "worker")]
struct Cli {
/// Caminho para o config TOML (padrão: ./worker.toml)
#[arg(short, long, env = "WORKER_CONFIG")]
config: Option<PathBuf>,
/// Substituir nível de log
#[arg(long, env = "RUST_LOG")]
log_level: Option<String>,
/// Substituir concorrência (prevalece sobre o arquivo de config)
#[arg(long)]
jobs: Option<usize>,
}
#[derive(Debug, Deserialize)]
struct FileConfig {
jobs: usize,
queue_url: String,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let path = cli.config.unwrap_or_else(|| PathBuf::from("worker.toml"));
let file: FileConfig = toml::from_str(&std::fs::read_to_string(&path)?)?;
let jobs = cli.jobs.unwrap_or(file.jobs);
eprintln!("executando com {jobs} jobs em {}", file.queue_url);
Ok(())
}O que isso demonstra:
- Flags substituem a configuração do arquivo (intenção explícita do operador)
env =em campos clap vincula variáveis de ambiente- Arquivo TOML contém padrões estáveis
- Precedência: flags > env > arquivo de config > padrões embutidos
Mergulho Profundo
Precedência Recomendada
- Flags da CLI (mais alta)
- Variáveis de ambiente
- Arquivo de configuração (TOML, YAML ou JSON)
- Padrões embutidos (mais baixa)
figment vs Crate config
| Crate | Força |
|---|---|
figment | Provedores Componíveis, ordem de mesclagem é explícita |
config | Chaves hierárquicas, bom para aplicativos grandes |
clap sozinho | Bom quando você só precisa de flags + env |
Segredos
// Nunca leia segredos de arquivos de configuração commitados no git
let api_key = std::env::var("API_KEY")
.context("defina API_KEY no ambiente")?;Armadilhas
- Colisões de nomes de variáveis de ambiente -
PORTpode conflitar com outras ferramentas. Correção: Prefixe com o nome do seu aplicativo (APP_PORT). - Arquivos de configuração ausentes silenciosamente - Arquivo ausente cai nos padrões inesperadamente. Correção: Registre qual arquivo foi carregado; falhe se o arquivo necessário estiver ausente.
- Surpresas na coerção de tipos - Variáveis de ambiente são sempre strings. Correção: Analise explicitamente com mensagens de erro claras.
- Caminhos de configuração relativos - Caminhos são resolvidos a partir do CWD, não da localização do binário. Correção: Documente as expectativas do CWD ou resolva em relação ao diretório pai do arquivo de configuração.
- Impressão de configuração na inicialização - Logs de depuração podem vazar segredos. Correção: Redija campos sensíveis antes de registrar.
Alternativas
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
| Apenas flags | Ferramentas de binário único com poucas configurações | Muitos ajustes em diferentes ambientes |
dotenvy para desenvolvimento | Arquivos .env locais durante o desenvolvimento | Gerenciamento de segredos de produção |
| Configuração remota (Consul, SSM) | Frota de serviços com atualizações ao vivo | CLIs simples de host único |
FAQs
Qual formato de configuração devo usar?
TOML é o padrão do ecossistema Rust. YAML funciona quando sua equipe de operações já o padroniza.
As flags devem espelhar as chaves de configuração?
Sim. Use os mesmos nomes (--port mapeia para port no arquivo) para reduzir a carga cognitiva.
Como suportar diretórios de configuração XDG?
Use dirs::config_dir() e recorra a ./config.toml para desenvolvimento.
O `clap` pode ler um arquivo de configuração diretamente?
O clap 4 não carrega arquivos nativamente. Mescle um provedor de arquivo antes ou depois de parse().
Como testar o carregamento de configuração?
Escreva arquivos TOML temporários em testes e passe caminhos via Cli::parse_from.
Múltiplos ambientes?
Use config.dev.toml / config.prod.toml selecionados por --env ou APP_ENV.
Validar configuração na inicialização?
Desserializar em structs tipadas com serde. Falhe rapidamente com anyhow::Context em campos ausentes.
Variáveis de ambiente booleanas?
Use env = "FLAG" com Option<bool> ou analise strings "true" / "false" explicitamente.
Recarregamento a quente do arquivo de configuração?
Raro para CLIs. Se necessário, monitore o arquivo com notify e recarregue na mudança.
Documentar a precedência?
Imprima uma nota de --help e uma linha de log de inicialização mostrando as fontes de configuração ativas.
Relacionado
- Fundamentos de CLI - fundamentos
- clap - vinculação de flags e env
- Relatório de Erros - mensagens de erro de configuração
- Empacotamento e Distribuição - envio de padrões
Versões da Stack: Esta página foi escrita para Rust 1.97.0 (edição 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, e Polars 0.46+.