Entendendo Variáveis de Ambiente em Rust
Variáveis de ambiente são pares chave-valor armazenados no sistema operacional que sua aplicação pode acessar em tempo de execução. Em Rust, elas são fundamentais para gerenciar configurações sensíveis (senhas, tokens API) e adaptar o comportamento da aplicação sem recompilar o código. A diferença crítica entre hard-coding valores e usar variáveis de ambiente é que estas permitem que a mesma compilação rode em desenvolvimento, testes e produção com diferentes configurações.
A abordagem nativa do Rust é utilizar o módulo std::env, que oferece funções simples para acessar essas variáveis. No entanto, para aplicações profissionais, usamos a crate dotenv para carregar variáveis de um arquivo .env local, mantendo segredos fora do controle de versão. Compreender essa distinção é essencial: std::env lê do sistema, enquanto dotenv carrega de um arquivo antes da execução.
Usando std::env para Acesso Básico
Leitura Simples de Variáveis
O módulo padrão oferece std::env::var() que retorna um Result<String, VarError>. Aqui está um exemplo prático:
use std::env;
fn main() {
// Leitura segura com tratamento de erro
match env::var("DATABASE_URL") {
Ok(url) => println!("Conectando a: {}", url),
Err(e) => eprintln!("Erro: {}", e),
}
// Leitura com valor padrão
let port = env::var("PORT")
.unwrap_or_else(|_| "8080".to_string());
println!("Servidor rodando na porta: {}", port);
}
Execute assim no terminal:
DATABASE_URL="postgres://localhost/mydb" PORT=3000 cargo run
Iterando Todas as Variáveis
Às vezes precisamos processar todas as variáveis de uma vez:
use std::env;
fn main() {
for (key, value) in env::vars() {
println!("{}: {}", key, value);
}
}
Gerenciamento Profissional com dotenv
Carregando Arquivo .env
Para projetos reais, a crate dotenv simplifica o gerenciamento:
[dependencies]
dotenv = "0.15"
Crie um arquivo .env na raiz do projeto:
DATABASE_URL=postgresql://user:password@localhost/mydb
API_KEY=seu_token_secreto_aqui
ENVIRONMENT=development
DEBUG=true
E carregue em seu código:
use dotenv::dotenv;
use std::env;
fn main() {
dotenv().ok(); // Carrega .env, ignora erro se não existir
let database_url = env::var("DATABASE_URL")
.expect("DATABASE_URL não configurada");
let api_key = env::var("API_KEY")
.expect("API_KEY não configurada");
println!("Banco: {}", database_url);
println!("API Key carregada com sucesso");
}
Adicione .env ao .gitignore para nunca commitar segredos:
.env
.env.local
Estrutura de Configuração com Tipo Customizado
Para aplicações maiores, encapsule configurações em uma struct:
use dotenv::dotenv;
use std::env;
pub struct Config {
pub database_url: String,
pub api_key: String,
pub port: u16,
pub debug: bool,
}
impl Config {
pub fn from_env() -> Self {
dotenv().ok();
Config {
database_url: env::var("DATABASE_URL")
.expect("DATABASE_URL deve estar definida"),
api_key: env::var("API_KEY")
.expect("API_KEY deve estar definida"),
port: env::var("PORT")
.unwrap_or_else(|_| "8080".to_string())
.parse()
.expect("PORT deve ser um número válido"),
debug: env::var("DEBUG")
.map(|v| v.to_lowercase() == "true")
.unwrap_or(false),
}
}
}
fn main() {
let config = Config::from_env();
println!("Conectando a: {}", config.database_url);
println!("Debug ativo: {}", config.debug);
println!("Servidor na porta: {}", config.port);
}
Este padrão escalável é usado em frameworks como Actix e Rocket. Você centraliza a lógica de validação em um único lugar, facilitando testes e manutenção.
Validação e Tratamento de Erros
Custom Error Handling
Erros de configuração devem ser claros. Use enums para melhor controle:
use std::env;
#[derive(Debug)]
pub enum ConfigError {
MissingVar(String),
InvalidValue(String),
}
pub fn load_config() -> Result<(String, u16), ConfigError> {
let db_url = env::var("DATABASE_URL")
.map_err(|_| ConfigError::MissingVar("DATABASE_URL".to_string()))?;
let port = env::var("PORT")
.unwrap_or_else(|_| "8080".to_string())
.parse::<u16>()
.map_err(|_| ConfigError::InvalidValue("PORT deve ser um número".to_string()))?;
Ok((db_url, port))
}
fn main() {
match load_config() {
Ok((db, port)) => println!("Config OK: {} on :{}", db, port),
Err(e) => eprintln!("Erro de configuração: {:?}", e),
}
}
Validação em Tempo de Compilação com Features
Use feature flags para diferentes ambientes:
[features]
default = ["dev"]
dev = []
prod = []
#[cfg(feature = "prod")]
const REQUIRE_HTTPS: bool = true;
#[cfg(feature = "dev")]
const REQUIRE_HTTPS: bool = false;
fn main() {
if REQUIRE_HTTPS {
println!("Modo produção: HTTPS obrigatório");
}
}
Compile com: cargo build --release --features prod
Conclusão
Dominar variáveis de ambiente em Rust envolve três pilares: (1) Usar std::env para acesso direto ao sistema operacional em casos simples, e dotenv para gerenciar arquivos .env locais em desenvolvimento; (2) Encapsular configuração em structs customizadas que centralizam validação e lógica, tornando código profissional e testável; (3) Implementar tratamento robusto de erros com tipos customizados e feature flags para distinguir ambientes de desenvolvimento e produção.
Esses padrões eliminam hard-coding, protegem segredos e tornam suas aplicações Rust prontas para produção desde o design inicial.