Introdução: por que um singleton global em Rust é um desafio?
\n\nSe você vem do mundo Python, JavaScript ou C++, está acostumado a um simples objeto global. Declarou uma variável no nível do módulo — e ela é sua. Em Rust, esse truque não funciona. O compilador monitora rigorosamente o tempo de vida e o acesso aos dados, especialmente se eles podem ser alterados por diferentes threads. É por isso que no Stack Overflow a pergunta «How do I create a global, mutable singleton?» acumulou milhares de visualizações e dezenas de respostas complexas.
\n\nMas não se assuste. Singleton em Rust não é dor, é disciplina. Hoje vamos analisar como criar um singleton global mutável de forma segura, eficiente e com compreensão do que está acontecendo. Você aprenderá por que static mut é mau e quais ferramentas (lazy_static, OnceCell, Mutex) nos dão controle.
O artigo foi escrito para o site educacional pythonlib.ru e adapta uma pergunta real do SO para uma aula prática. Vamos nessa!
\n\nO problema: o que é um singleton global mutável?
\n\nSingleton é um padrão no qual uma classe ou estrutura tem exatamente uma instância em todo o programa. No contexto do Rust, estamos falando de uma variável global, acessível de qualquer lugar do código, que pode ser alterada (mut).
\n\nExemplos de uso:
\n- \n
- Configuração da aplicação, carregada uma vez na inicialização. \n
- Logger global ou contador de eventos. \n
- Pool de conexões com o banco de dados. \n
- Cache em memória (in-memory cache). \n
O problema é que o Rust proíbe variáveis globais mutáveis sem um wrapper especial. Por quê? Porque elas levam a condições de corrida (data races) e comportamento indefinido. O compilador não consegue provar que duas threads não escreverão simultaneamente no mesmo local de memória.
\n\nA solução ingênua é assim:
\n// Código RUIM — não faça isso!\nstatic mut COUNTER: u32 = 0;\n\nfn increment() {\n unsafe {\n COUNTER += 1;\n }\n}\n\nIsso funciona, mas requer unsafe e deixa você sozinho com as condições de corrida. Em um ambiente multithread, é uma bomba-relógio. Precisamos de uma abordagem segura e idiomática.
Ferramenta 1: lazy_static + Mutex — o clássico do gênero
\n\nA maneira mais popular de criar um singleton global mutável é a combinação de lazy_static (inicialização preguiçosa) e Mutex (exclusão mútua).
\n\nAdicione as dependências no Cargo.toml:
[dependencies]\nlazy_static = "1.4"\n\n\nAgora o código:
\nuse lazy_static::lazy_static;\nuse std::sync::Mutex;\n\nlazy_static! {\n static ref GLOBAL_CONFIG: Mutex<Config> = Mutex::new(Config::default());\n}\n\n#[derive(Debug)]\nstruct Config {\n db_url: String,\n max_connections: u32,\n}\n\nimpl Default for Config {\n fn default() -> Self {\n Config {\n db_url: "localhost:5432".to_string(),\n max_connections: 10,\n }\n }\n}\n\nfn main() {\n // Leitura\n let config = GLOBAL_CONFIG.lock().unwrap();\n println!("URL atual: {}", config.db_url);\n\n // Escrita\n // drop(config); // libera explicitamente o bloqueio\n let mut config = GLOBAL_CONFIG.lock().unwrap();\n config.db_url = "new-host:8888".to_string();\n println!("Nova URL: {}", config.db_url);\n}\n\n\nComo funciona?
\n- \n
lazy_static!garante que a inicialização ocorra apenas uma vez, no primeiro acesso. \n Mutex<T>fornece exclusão mútua: apenas uma thread pode acessar os dados por vez. \n - O método
.lock()retorna umMutexGuard, que libera automaticamente o bloqueio ao sair do escopo. \n
Vantagens: simplicidade, testado pelo tempo, seguro para