About Singleton

고승우·2025년 8월 8일

1. What is the Singleton Pattern?

The Singleton Pattern is a creational design pattern that ensures a class has only one instance throughout the application’s lifecycle and provides a global access point to it.

In other words:

Only one object exists, and it is shared across the entire program.


2. Key Characteristics

  • Single instance guarantee – only one object is ever created.
  • Global access – easy access from anywhere in the application.
  • Controlled initialization – choose between eager or lazy initialization.
  • Language-agnostic – implemented in Java, Rust, C++, Python, and more.

3. Why Use Singleton?

  • Consistency: Multiple instances of certain resources (like config, database connection, logger) can cause conflicts or inconsistencies.
  • Performance: Prevents unnecessary memory allocations for resources that are used application-wide.
  • Centralized control: Makes it easy to modify shared settings in one place.

4. Rust Singleton Example – Application Configuration

Below is a practical Singleton implementation for managing application configuration in Rust using OnceLock for thread-safe, one-time initialization.

rust
CopyEdit
use crate::errors::ServiceError;
use std::sync::OnceLock;

pub struct Config {
    pub database_url: String,
    pub rocksdb_path: String,
    pub rocksdb_buffer_size: usize,
    pub gmail_username: String,
    pub gmail_app_password: String,
    pub jwt_secret: String,
    pub architector_server_url: String,
}

impl Config {
    fn new() -> Result<Config, ServiceError> {
        dotenv::dotenv().ok();
        Ok(Config {
            database_url: std::env::var("DATABASE_URL").unwrap(),
            rocksdb_path: std::env::var("ROCKSDB_PATH")
                .unwrap_or_else(|_| "./rocksdb_data".to_string()),
            rocksdb_buffer_size: std::env::var("ROCKSDB_BUFFER_SIZE")
                .unwrap_or_else(|_| (1024 * 1024).to_string())
                .parse()
                .unwrap(),
            gmail_username: std::env::var("GMAIL_USERNAME").unwrap(),
            gmail_app_password: std::env::var("GMAIL_APP_PASSWORD").unwrap(),
            jwt_secret: std::env::var("JWT_SECRET")
                .unwrap_or_else(|_| "your-secret-key".to_string()),
            architector_server_url: std::env::var("ARCHITECTOR_SERVER_URL")
                .unwrap_or_else(|_| "http://localhost:8000".to_string()),
        })
    }
}

// Singleton accessor
pub fn get_config() -> &'static Config {
    static CONFIG: OnceLock<Config> = OnceLock::new();
    CONFIG.get_or_init(|| Config::new().unwrap())
}

About OnceLock

pub struct OnceLock<T> {
    once: Once,
    value: UnsafeCell<MaybeUninit<T>>,
    _marker: PhantomData<T>,
}

OnceLock<T> is a thread-safe cell for one-time initialization. The first call to get_or_init() runs the initializer exactly once, and all later calls return the same reference without locking. It’s ideal for implementing global Singletons in Rust.


5. How It Works

  1. The first call to get_config() triggers Config::new().
  2. Any subsequent calls simply return the same reference.
  3. Accessible globally without passing the object around manually.

6. Example Usage

fn main() {
    let config = get_config();
    println!("DB URL: {}", config.database_url);
}

Here, config is always the same instance across the entire program.


7. Advantages

  • Resource consistency – configuration, database connections, logging, etc.
  • Memory efficiency – prevents duplicate instances of heavy resources.
  • Thread-safetyOnceLock guarantees safe initialization in concurrent contexts.

8. Drawbacks

  • Global state issues – makes unit testing harder due to hidden dependencies.
  • Tight coupling – can violate the Single Responsibility Principle (SRP) and Open/Closed Principle (OCP).
  • Less flexibility – compared to dependency injection (DI) approaches.

9. When to Use Singleton

Use it when:

  • You need exactly one instance of a resource (e.g., config, logger, cache).
  • The resource is costly to initialize and should be shared.

Avoid it when:

  • Different parts of the application need different configurations.
  • You want easier unit testing with mock objects.

10. Conclusion

The Singleton Pattern is a powerful yet double-edged tool.

In Rust, OnceLock makes it possible to implement this pattern elegantly and safely. By sharing a single instance across the application, Singleton ensures consistent behavior, efficient resource usage, and simplified code management.

profile
٩( ᐛ )و 

0개의 댓글