Como Ler Variáveis de Ambiente em Rust

Aprenda a ler variáveis de ambiente em Rust com env::var, dotenvy, padrão de config tipada, variáveis obrigatórias vs opcionais. Código completo e executável.

Variáveis de ambiente são a forma padrão de configurar aplicações em ambientes de produção, containers Docker e plataformas de deploy. Rust oferece std::env::var na biblioteca padrão, e a crate dotenvy adiciona suporte a arquivos .env para desenvolvimento local. Nesta receita, você vai aprender a ler, validar e organizar variáveis de ambiente em Rust.

Dependências

[package]
name = "receita-env"
version = "0.1.0"
edition = "2021"

[dependencies]
dotenvy = "0.15"

Código Completo

use std::env;
use std::net::SocketAddr;

fn main() {
    // =============================================
    // 1. env::var() — leitura básica
    // =============================================
    println!("=== Leitura Básica ===");

    // Variáveis do sistema (geralmente disponíveis)
    match env::var("HOME") {
        Ok(valor) => println!("HOME: {}", valor),
        Err(e) => println!("HOME não definida: {}", e),
    }

    match env::var("PATH") {
        Ok(valor) => {
            let paths: Vec<&str> = valor.split(':').take(3).collect();
            println!("PATH (3 primeiros): {:?}", paths);
        }
        Err(e) => println!("PATH não definida: {}", e),
    }

    // Variável que provavelmente não existe
    match env::var("MINHA_VARIAVEL_INEXISTENTE") {
        Ok(valor) => println!("Valor: {}", valor),
        Err(env::VarError::NotPresent) => println!("MINHA_VARIAVEL: não definida"),
        Err(env::VarError::NotUnicode(_)) => println!("MINHA_VARIAVEL: valor inválido"),
    }

    // =============================================
    // 2. dotenvy — carregar arquivo .env
    // =============================================
    println!("\n=== dotenvy (.env) ===");

    // Criar um .env de exemplo para demonstração
    std::fs::write("/tmp/.env.exemplo", r#"
DATABASE_URL=postgresql://localhost:5432/meudb
SECRET_KEY=minha-chave-super-secreta-2026
APP_PORT=8080
APP_DEBUG=true
APP_HOST=0.0.0.0
LOG_LEVEL=info
REDIS_URL=redis://localhost:6379
MAX_CONEXOES=50
"#.trim()).unwrap();

    // Carregar .env (ok se não existir)
    match dotenvy::from_path("/tmp/.env.exemplo") {
        Ok(_) => println!("Arquivo .env carregado"),
        Err(e) => println!("Sem .env: {}", e),
    }

    // Agora as variáveis estão disponíveis via env::var
    if let Ok(url) = env::var("DATABASE_URL") {
        println!("DATABASE_URL: {}", url);
    }
    if let Ok(port) = env::var("APP_PORT") {
        println!("APP_PORT: {}", port);
    }

    // Limpar arquivo de teste
    let _ = std::fs::remove_file("/tmp/.env.exemplo");

    // =============================================
    // 3. Variáveis obrigatórias vs opcionais
    // =============================================
    println!("\n=== Obrigatórias vs Opcionais ===");

    // Obrigatória — panic se não existir (ok para inicialização)
    fn env_obrigatoria(nome: &str) -> String {
        env::var(nome).unwrap_or_else(|_| {
            panic!("Variável de ambiente '{}' é obrigatória", nome)
        })
    }

    // Opcional com valor padrão
    fn env_opcional(nome: &str, padrao: &str) -> String {
        env::var(nome).unwrap_or_else(|_| padrao.to_string())
    }

    // Opcional tipada
    fn env_parse<T: std::str::FromStr>(nome: &str, padrao: T) -> T {
        env::var(nome)
            .ok()
            .and_then(|v| v.parse().ok())
            .unwrap_or(padrao)
    }

    let host = env_opcional("APP_HOST", "127.0.0.1");
    let porta: u16 = env_parse("APP_PORT", 3000);
    let debug: bool = env_parse("APP_DEBUG", false);
    let log_level = env_opcional("LOG_LEVEL", "warn");
    let max_conn: u32 = env_parse("MAX_CONEXOES", 10);

    println!("Host: {}", host);
    println!("Porta: {}", porta);
    println!("Debug: {}", debug);
    println!("Log Level: {}", log_level);
    println!("Max Conexões: {}", max_conn);

    // =============================================
    // 4. Padrão Config struct
    // =============================================
    println!("\n=== Config Struct ===");

    #[derive(Debug)]
    struct Config {
        host: String,
        porta: u16,
        database_url: String,
        secret_key: String,
        debug: bool,
        log_level: String,
        max_conexoes: u32,
        redis_url: Option<String>,
    }

    #[derive(Debug)]
    enum ConfigErro {
        Ausente(String),
        Invalido(String, String),
    }

    impl std::fmt::Display for ConfigErro {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            match self {
                ConfigErro::Ausente(nome) => {
                    write!(f, "Variável '{}' é obrigatória", nome)
                }
                ConfigErro::Invalido(nome, valor) => {
                    write!(f, "Valor inválido para '{}': '{}'", nome, valor)
                }
            }
        }
    }

    impl Config {
        fn from_env() -> Result<Config, ConfigErro> {
            let database_url = env::var("DATABASE_URL")
                .map_err(|_| ConfigErro::Ausente("DATABASE_URL".into()))?;

            let secret_key = env::var("SECRET_KEY")
                .map_err(|_| ConfigErro::Ausente("SECRET_KEY".into()))?;

            let porta_str = env::var("APP_PORT").unwrap_or_else(|_| "8080".into());
            let porta: u16 = porta_str.parse()
                .map_err(|_| ConfigErro::Invalido("APP_PORT".into(), porta_str))?;

            Ok(Config {
                host: env::var("APP_HOST").unwrap_or_else(|_| "0.0.0.0".into()),
                porta,
                database_url,
                secret_key,
                debug: env::var("APP_DEBUG")
                    .map(|v| v == "true" || v == "1")
                    .unwrap_or(false),
                log_level: env::var("LOG_LEVEL").unwrap_or_else(|_| "info".into()),
                max_conexoes: env::var("MAX_CONEXOES")
                    .ok()
                    .and_then(|v| v.parse().ok())
                    .unwrap_or(10),
                redis_url: env::var("REDIS_URL").ok(),
            })
        }

        fn socket_addr(&self) -> SocketAddr {
            format!("{}:{}", self.host, self.porta)
                .parse()
                .expect("Endereço de socket inválido")
        }
    }

    // Demonstrar o padrão Config
    match Config::from_env() {
        Ok(config) => {
            println!("Config carregada:");
            println!("  Endereço: {}", config.socket_addr());
            println!("  DB: {}", config.database_url);
            println!("  Debug: {}", config.debug);
            println!("  Log: {}", config.log_level);
            println!("  Redis: {:?}", config.redis_url);
            println!("  Max conexões: {}", config.max_conexoes);
        }
        Err(e) => println!("Erro na config: {}", e),
    }

    // =============================================
    // 5. Listar todas as variáveis de ambiente
    // =============================================
    println!("\n=== Variáveis APP_* ===");
    for (chave, valor) in env::vars() {
        if chave.starts_with("APP_") || chave.starts_with("DATABASE") {
            println!("  {} = {}", chave, valor);
        }
    }

    // =============================================
    // 6. Definir variáveis em tempo de execução
    // =============================================
    println!("\n=== Definir Variáveis ===");
    env::set_var("MINHA_VAR", "hello");
    println!("MINHA_VAR = {:?}", env::var("MINHA_VAR"));

    env::remove_var("MINHA_VAR");
    println!("Após remove: {:?}", env::var("MINHA_VAR"));

    // =============================================
    // 7. Variáveis de tempo de compilação
    // =============================================
    println!("\n=== Variáveis de Compilação ===");
    println!("Versão do pacote: {}", env!("CARGO_PKG_VERSION"));
    println!("Nome do pacote:   {}", env!("CARGO_PKG_NAME"));

    // option_env! — não causa panic se não existir
    match option_env!("BUILD_SHA") {
        Some(sha) => println!("Build SHA: {}", sha),
        None => println!("Build SHA: não definido"),
    }
}

Saída do Programa

=== Leitura Básica ===
HOME: /root
PATH (3 primeiros): ["/usr/local/bin", "/usr/bin", "/bin"]
MINHA_VARIAVEL: não definida

=== dotenvy (.env) ===
Arquivo .env carregado
DATABASE_URL: postgresql://localhost:5432/meudb
APP_PORT: 8080

=== Obrigatórias vs Opcionais ===
Host: 0.0.0.0
Porta: 8080
Debug: true
Log Level: info
Max Conexões: 50

=== Config Struct ===
Config carregada:
  Endereço: 0.0.0.0:8080
  DB: postgresql://localhost:5432/meudb
  Debug: true
  Log: info
  Redis: Some("redis://localhost:6379")
  Max conexões: 50

=== Variáveis APP_* ===
  APP_PORT = 8080
  APP_DEBUG = true
  APP_HOST = 0.0.0.0
  DATABASE_URL = postgresql://localhost:5432/meudb

=== Definir Variáveis ===
MINHA_VAR = Ok("hello")
Após remove: Err(NotPresent)

=== Variáveis de Compilação ===
Versão do pacote: 0.1.0
Nome do pacote:   receita-env
Build SHA: não definido

Exemplo de Arquivo .env

# Banco de dados
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb

# Servidor
APP_HOST=0.0.0.0
APP_PORT=8080
APP_DEBUG=true

# Segurança
SECRET_KEY=minha-chave-secreta-muito-longa

# Logging
LOG_LEVEL=debug

# Serviços externos
REDIS_URL=redis://localhost:6379

Boas Práticas

  • Nunca commite o .env no Git — adicione ao .gitignore. Use .env.example como referência.
  • Valide variáveis na inicialização — falhe cedo com mensagens claras se algo está faltando.
  • Use o padrão Config struct — centralize todas as variáveis em uma struct validada.
  • dotenvy para desenvolvimento, env vars para produção — em produção, configure via Docker, systemd ou plataforma de deploy.
  • env!() para valores de compilação — use para versão do pacote, nome e hash do build.

Veja Também