Singleton global em Rust: quebrando estereótipos com código

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: por que um singleton global em Rust é um desafio?

\n\n

Se 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\n

Mas 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.

\n\n

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\n

O problema: o que é um singleton global mutável?

\n\n

Singleton é 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\n

Exemplos 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
\n\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\n

A 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
\n

Isso 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.

\n\n

Ferramenta 1: lazy_static + Mutex — o clássico do gênero

\n\n

A 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\n

Adicione as dependências no Cargo.toml:

\n
[dependencies]\nlazy_static = "1.4"\n
\n\n

Agora o código:

\n
use 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\n

Como 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 um MutexGuard, que libera automaticamente o bloqueio ao sair do escopo.
  • \n
\n\n

Vantagens: simplicidade, testado pelo tempo, seguro para

Blogs

Book Recommendations