Rust

How do I create a global mutable singleton

25 September 2026 · 10 min read

How do I create a global mutable singleton

Creating a global, mutable singleton is a common design pattern in software development, offering a convenient way to manage shared resources or configurations. It ensures that a class has only one instance, accessible globally throughout your application, and that this instance can be modified as needed. However, understanding the implications and proper implementation is crucial to avoid potential pitfalls like unexpected behavior and concurrency issues. This article dives deep into the creation and usage of global, mutable singletons, exploring best practices and addressing common concerns.

Understanding the Singleton Pattern

At its core, the singleton pattern restricts a class to a single instance and provides a global point of access to it. This is particularly useful when you need a single, shared resource, such as a database connection, a configuration manager, or a logging system. Mutability adds another layer to this, allowing the singleton’s state to change throughout the application’s lifecycle.

While convenient, mutable singletons can introduce challenges in testing and maintaining code due to their global accessibility and changeable state. Therefore, careful consideration of its use case is paramount.

For instance, imagine a scenario where you’re building a game and need a single, global instance of a game state manager. This manager would hold information like the current score, player health, and level progress. Mutability allows these values to be updated as the game progresses, and the global access ensures that any part of the game can access and modify this central state.

Implementing a Global, Mutable Singleton in Python

Python provides elegant ways to implement this pattern. Here’s a common approach:

class Singleton: _instance = None def __new__(cls, args, kwargs): if not cls._instance: cls._instance = super().__new__(cls, args, kwargs) return cls._instance def __init__(self, data): if not hasattr(self, '_initialized'): self.data = data self._initialized = True 

This code uses the __new__ method to control instance creation, ensuring only one instance exists. The __init__ method initializes the singleton’s data. The _initialized flag prevents re-initialization if the singleton is accessed multiple times. Let’s illustrate with an example:

s1 = Singleton({"name": "John Doe"}) s2 = Singleton({"name": "Jane Doe"}) print(s1.data) Output: {'name': 'John Doe'} print(s2.data) Output: {'name': 'John Doe'} s1.data["age"] = 30 print(s2.data) Output: {'name': 'John Doe', 'age': 30} 

As demonstrated, s1 and s2 refer to the same instance, and modifying one affects the other.

Addressing Potential Pitfalls

While powerful, global mutable singletons require careful handling. Excessive use can lead to tight coupling and hinder testability. One approach to mitigate this is dependency injection, where instead of directly accessing the singleton, classes receive it as a parameter. This improves modularity and makes testing easier.

Concurrency is another concern. If multiple threads access and modify the singleton concurrently, race conditions can occur. Employing appropriate locking mechanisms, like threading.Lock in Python, can prevent such issues. Always prioritize thread safety when using mutable singletons in a multi-threaded environment. Consider the impact on performance and choose the appropriate locking strategy.

Here’s a simplified example of thread-safe implementation:

import threading class ThreadSafeSingleton: _instance = None _lock = threading.Lock() ... (rest of the code with locking in __new__ and relevant methods) 

Alternatives to Mutable Singletons

Consider alternatives like dependency injection or application-level contexts. Dependency injection promotes modularity and testability by explicitly passing dependencies to objects. Application contexts, available in frameworks like Flask and Django, provide a centralized way to manage shared resources.

Choosing the right pattern depends on the specific requirements of your application. For complex applications, dependency injection or application contexts often provide better maintainability and scalability compared to global mutable singletons. For smaller applications or specific use cases where global access is genuinely required, a carefully implemented mutable singleton can be a viable solution.

Key Considerations for Choosing a Pattern

  • Application complexity
  • Testability requirements
  • Scalability needs
  • Concurrency aspects

By considering these factors, you can make an informed decision about the most suitable approach for your specific project.

Real-World Example: Configuration Management

A common use case for a global, mutable singleton is managing application configuration. Imagine a scenario where you need to access and modify configuration settings from different parts of your application. A mutable singleton allows you to load these settings once and then modify them as needed, ensuring consistency throughout the application.

For instance, you could use a mutable singleton to store database credentials, API keys, or feature flags. This centralizes configuration management and makes it easier to update these settings without modifying numerous parts of the codebase. However, be mindful of security implications when storing sensitive information in a singleton.

  1. Define the singleton class.
  2. Implement the __new__ method to control instantiation.
  3. Initialize the singleton’s data within the __init__ method.
  4. Access and modify the singleton’s data through its methods.

This structured approach ensures that the singleton pattern is correctly implemented and that the singleton’s state is managed effectively.

[Infographic illustrating the singleton pattern and its usage]

This approach leverages the singleton pattern’s global accessibility and mutability, providing a consistent and easily manageable solution for configuration management.

Learn more about design patternsUnderstanding these tradeoffs allows developers to choose the pattern that best suits the project’s specific needs and constraints.

  • Global access can make testing and debugging more challenging.
  • Mutability introduces the risk of unintended side effects.

Frequently Asked Questions

Q: What are the main advantages of using a singleton?

A: Singletons provide a single point of access to a shared resource, ensuring consistency and potentially improving resource management.

Q: When should I avoid using a singleton?

A: Avoid singletons when testability is a high priority or when dealing with complex, multi-threaded environments where alternative patterns like dependency injection offer better control and maintainability.

Implementing global, mutable singletons requires careful planning and execution. By understanding the pattern’s strengths, weaknesses, and best practices, you can leverage its benefits while mitigating potential issues. Explore alternative patterns like dependency injection and application-level contexts to determine the best approach for your specific needs. Always prioritize thread safety in multi-threaded environments and use appropriate locking mechanisms. Consider using a factory pattern to create different variations of your singleton if needed. By carefully considering these aspects, you can create robust and maintainable applications that effectively utilize the singleton pattern where appropriate. Check out resources like Refactoring Guru, SourceMaking, and Real Python for further insights into design patterns and best practices.

Question & Answer :
What is the best way to create and use a struct with only one instantiation in the system? Yes, this is necessary, it is the OpenGL subsystem, and making multiple copies of this and passing it around everywhere would add confusion, rather than relieve it.

The singleton needs to be as efficient as possible. It doesn’t seem possible to store an arbitrary object on the static area, as it contains a Vec with a destructor. The second option is to store an (unsafe) pointer on the static area, pointing to a heap allocated singleton. What is the most convenient and safest way to do this, while keeping syntax terse?

Non-answer answer

Avoid global state in general. Instead, construct the object somewhere early (perhaps in main), then pass mutable references to that object into the places that need it. This will usually make your code easier to reason about and doesn’t require as much bending over backwards.

Look hard at yourself in the mirror before deciding that you want global mutable variables. There are rare cases where it’s useful, so that’s why it’s worth knowing how to do.

Still want to make one…?

Tips

In the following solutions:

  • If you remove the Mutex then you have a global singleton without any mutability.
  • You can also use a RwLock instead of a Mutex to allow multiple concurrent readers.

Using std::sync::LazyLock

LazyLock was stabilized as of Rust 1.80.0. It can be used to eliminate the inconvenience of OnceLock’s helper function:

use std::sync::{LazyLock, Mutex}; static ARRAY: LazyLock<Mutex<Vec<u8>>> = LazyLock::new(|| Mutex::new(vec![])); fn do_a_call() { ARRAY.lock().unwrap().push(1); } fn main() { do_a_call(); do_a_call(); do_a_call(); println!("called {}", ARRAY.lock().unwrap().len()); } 

Using std::sync::OnceLock

OnceLock was stabilized as of Rust 1.70.0. You can use it to get a dependency-free implementation:

use std::sync::{Mutex, OnceLock}; fn array() -> &'static Mutex<Vec<u8>> { static ARRAY: OnceLock<Mutex<Vec<u8>>> = OnceLock::new(); ARRAY.get_or_init(|| Mutex::new(vec![])) } fn do_a_call() { array().lock().unwrap().push(1); } fn main() { do_a_call(); do_a_call(); do_a_call(); println!("called {}", array().lock().unwrap().len()); } 

Using lazy-static

The lazy-static crate can take away some of the drudgery of manually creating a singleton. Here is a global mutable vector:

use lazy_static::lazy_static; // 1.4.0 use std::sync::Mutex; lazy_static! { static ref ARRAY: Mutex<Vec<u8>> = Mutex::new(vec![]); } fn do_a_call() { ARRAY.lock().unwrap().push(1); } fn main() { do_a_call(); do_a_call(); do_a_call(); println!("called {}", ARRAY.lock().unwrap().len()); } 

Using once_cell

The once_cell crate can take away some of the drudgery of manually creating a singleton. Here is a global mutable vector:

use once_cell::sync::Lazy; // 1.3.1 use std::sync::Mutex; static ARRAY: Lazy<Mutex<Vec<u8>>> = Lazy::new(|| Mutex::new(vec![])); fn do_a_call() { ARRAY.lock().unwrap().push(1); } fn main() { do_a_call(); do_a_call(); do_a_call(); println!("called {}", ARRAY.lock().unwrap().len()); } 

A special case: atomics

If you only need to track an integer value, you can directly use an atomic:

use std::sync::atomic::{AtomicUsize, Ordering}; static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); fn do_a_call() { CALL_COUNT.fetch_add(1, Ordering::SeqCst); } fn main() { do_a_call(); do_a_call(); do_a_call(); println!("called {}", CALL_COUNT.load(Ordering::SeqCst)); } 

Manual, dependency-free implementation

There are several existing implementation of statics, such as the Rust 1.0 implementation of stdin. This is the same idea adapted to modern Rust, such as the use of MaybeUninit to avoid allocations and unnecessary indirection. You should also look at the modern implementation of io::Lazy. I’ve commented inline with what each line does.

use std::sync::{Mutex, Once}; use std::time::Duration; use std::{mem::MaybeUninit, thread}; struct SingletonReader { // Since we will be used in many threads, we need to protect // concurrent access inner: Mutex<u8>, } fn singleton() -> &'static SingletonReader { // Create an uninitialized static static mut SINGLETON: MaybeUninit<SingletonReader> = MaybeUninit::uninit(); static ONCE: Once = Once::new(); unsafe { ONCE.call_once(|| { // Make it let singleton = SingletonReader { inner: Mutex::new(0), }; // Store it to the static var, i.e. initialize it SINGLETON.write(singleton); }); // Now we give out a shared reference to the data, which is safe to use // concurrently. SINGLETON.assume_init_ref() } } fn main() { // Let's use the singleton in a few threads let threads: Vec<_> = (0..10) .map(|i| { thread::spawn(move || { thread::sleep(Duration::from_millis(i * 10)); let s = singleton(); let mut data = s.inner.lock().unwrap(); *data = i as u8; }) }) .collect(); // And let's check the singleton every so often for _ in 0u8..20 { thread::sleep(Duration::from_millis(5)); let s = singleton(); let data = s.inner.lock().unwrap(); println!("It is: {}", *data); } for thread in threads.into_iter() { thread.join().unwrap(); } } 

This prints out:

It is: 0 It is: 1 It is: 1 It is: 2 It is: 2 It is: 3 It is: 3 It is: 4 It is: 4 It is: 5 It is: 5 It is: 6 It is: 6 It is: 7 It is: 7 It is: 8 It is: 8 It is: 9 It is: 9 It is: 9 

This code compiles with Rust 1.55.0.

All of this work is what lazy-static or once_cell do for you.

The meaning of “global”

Please note that you can still use normal Rust scoping and module-level privacy to control access to a static or lazy_static variable. This means that you can declare it in a module or even inside of a function and it won’t be accessible outside of that module / function. This is good for controlling access:

use lazy_static::lazy_static; // 1.2.0 fn only_here() { lazy_static! { static ref NAME: String = String::from("hello, world!"); } println!("{}", &*NAME); } fn not_here() { println!("{}", &*NAME); } 
error[E0425]: cannot find value `NAME` in this scope --> src/lib.rs:12:22 | 12 | println!("{}", &*NAME); | ^^^^ not found in this scope 

However, the variable is still global in that there’s one instance of it that exists across the entire program.