Singleton global en Rust: rompiendo estereotipos con código

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: ¿por qué un singleton global en Rust es un desafío?

\n\n

Si vienes del mundo de Python, JavaScript o C++, estás acostumbrado a un objeto global simple. Declaras una variable a nivel de módulo y es tuya. En Rust, ese truco no funciona. El compilador supervisa estrictamente el tiempo de vida y el acceso a los datos, especialmente si pueden cambiar desde diferentes hilos. Por eso, en Stack Overflow, la pregunta «How do I create a global, mutable singleton?» ha acumulado miles de visitas y decenas de respuestas complejas.

\n\n

Pero no te asustes. Un singleton en Rust no es un dolor, es disciplina. Hoy analizaremos cómo crear un singleton mutable global de forma segura, eficiente y con comprensión de lo que sucede. Aprenderás por qué static mut es malo y qué herramientas (lazy_static, OnceCell, Mutex) nos brindan control.

\n\n

Este artículo está escrito para el sitio educativo pythonlib.ru y adapta una pregunta real de SO a una lección práctica. ¡Vamos allá!

\n\n

El problema: ¿qué es un singleton mutable global?

\n\n

Un singleton es un patrón en el que una clase o estructura tiene exactamente una instancia en todo el programa. En el contexto de Rust, hablamos de una variable global accesible desde cualquier lugar del código, que se puede modificar (mut).

\n\n

Ejemplos de uso:

\n
    \n
  • Configuración de la aplicación, cargada una vez al inicio.
  • \n
  • Un registrador global o contador de eventos.
  • \n
  • Un pool de conexiones a la base de datos.
  • \n
  • Un caché en memoria (in-memory cache).
  • \n
\n\n

El problema es que Rust prohíbe las variables globales mutables sin un envoltorio especial. ¿Por qué? Porque conducen a condiciones de carrera (data races) y comportamiento indefinido. El compilador no puede demostrar que dos hilos no escribirán simultáneamente en la misma celda de memoria.

\n\n

La solución ingenua se ve así:

\n
// Código MALO — ¡no lo hagas!\nstatic mut COUNTER: u32 = 0;\n\nfn increment() {\n    unsafe {\n        COUNTER += 1;\n    }\n}\n
\n

Esto funciona, pero requiere unsafe y te deja solo frente a las condiciones de carrera. En un entorno multihilo, es una bomba de tiempo. Necesitamos un enfoque seguro e idiomático.

\n\n

Herramienta 1: lazy_static + Mutex — el clásico del género

\n\n

La forma más popular de crear un singleton mutable global es la combinación de lazy_static (inicialización perezosa) y Mutex (exclusión mutua).

\n\n

Añade las dependencias en Cargo.toml:

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

Ahora el 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    // Lectura\n    let config = GLOBAL_CONFIG.lock().unwrap();\n    println!("URL actual: {}", config.db_url);\n\n    // Escritura\n    // drop(config); // liberamos explícitamente el bloqueo\n    let mut config = GLOBAL_CONFIG.lock().unwrap();\n    config.db_url = "new-host:8888".to_string();\n    println!("Nueva URL: {}", config.db_url);\n}\n
\n\n

¿Cómo funciona?

\n
    \n
  • lazy_static! garantiza que la inicialización ocurra solo una vez en el primer acceso.
  • \n
  • Mutex<T> proporciona exclusión mutua: solo un hilo puede acceder a los datos en un momento dado.
  • \n
  • El método .lock() devuelve un MutexGuard, que libera automáticamente el bloqueo al salir del ámbito.
  • \n
\n\n

Ventajas: simplicidad, probado en el tiempo, seguro para

Blogs

Book Recommendations