singleton-registry

Crates.iosingleton-registry
lib.rssingleton-registry
version2.0.0
created_at2025-08-16 15:22:56.390866+00
updated_at2025-10-11 06:43:09.164159+00
descriptionA thread-safe singleton registry for Rust with isolated, per-type storage
homepagehttps://github.com/dominikj111/singleton-registry
repositoryhttps://github.com/dominikj111/singleton-registry
max_upload_size
id1798550
size99,091
Dominik (dominikj111)

documentation

https://docs.rs/singleton-registry

README

singleton-registry

A thread-safe singleton registry for Rust.
Create isolated registries for storing and retrieving any type.
Each type can have only one instance per registry.

⚠️ Breaking Change in v2.0: This version uses a macro-based API. If you're upgrading from v1.x, you'll need to use define_registry! to create registries instead of using global functions. See the Quick Start below for the new API.

Features & Design

  • Synchronous API: No async/await complexity - simple function calls
  • Thread-safe: All operations safe across multiple threads using Arc and Mutex
  • Isolated registries: Create multiple independent registries with define_registry! - no hidden globals
  • True singleton: Only one instance per type per registry
  • Write-once pattern: Designed for initialization-time registration with optional runtime overrides
  • No removal: Values can be overridden but not removed - provide default values for fail-safe operation
  • Override-friendly: Later registrations replace previous ones
  • Tracing support: Optional callback system for monitoring operations

Quick Start

use singleton_registry::define_registry;
use std::sync::Arc;

define_registry!(global);
define_registry!(cache);

global::register("Hello, World!".to_string());
cache::register(42i32);

let message: Arc<String> = global::get().unwrap();
let number: Arc<i32> = cache::get().unwrap();

assert_eq!(&*message, "Hello, World!");
assert_eq!(*number, 42);

Advanced Usage

use singleton_registry::define_registry;
use std::sync::Arc;

define_registry!(app);

app::set_trace_callback(|event| {
    println!("Registry event: {}", event);
});

app::register(12i32);
app::register("config".to_string());

let multiply_by_two: fn(i32) -> i32 = |x| x * 2;
app::register(multiply_by_two);

assert!(app::contains::<i32>().unwrap());

let number: Arc<i32> = app::get().unwrap();
let config: Arc<String> = app::get().unwrap();
let doubler: Arc<fn(i32) -> i32> = app::get().unwrap();

let result = doubler(21);

assert_eq!(result, 42);
assert_eq!(*number, 12);
assert_eq!(&*config, "config");

Multiple Isolated Registries

use singleton_registry::define_registry;

define_registry!(database);
define_registry!(cache);
define_registry!(config);

database::register("postgresql://localhost".to_string());
cache::register("redis://localhost".to_string());
config::register("app_config".to_string());

let db_conn = database::get::<String>().unwrap();
let cache_conn = cache::get::<String>().unwrap();

API Reference

Each registry created with define_registry!(name) provides:

  • name::register(value) - Register a value
  • name::register_arc(arc_value) - Register an Arc-wrapped value
  • name::get::<T>() - Retrieve a value as Arc<T> (returns Result)
  • name::get_cloned::<T>() - Retrieve a cloned value (requires Clone, returns Result)
  • name::contains::<T>() - Check if a type is registered (returns Result)
  • name::set_trace_callback(callback) - Set up tracing
  • name::clear_trace_callback() - Clear tracing

Error Handling

All fallible operations return Result<T, RegistryError>:

pub enum RegistryError {
    /// Type not found in the registry
    TypeNotFound { type_name: &'static str },

    /// Type mismatch during retrieval (should never happen)
    TypeMismatch { type_name: &'static str },

    /// Failed to acquire registry lock (automatically recovered)
    RegistryLock,
}

Example:

use singleton_registry::define_registry;

define_registry!(app);

// Handle errors explicitly
match app::get::<String>() {
    Ok(value) => println!("Found: {}", value),
    Err(e) => eprintln!("Error: {}", e),  // "Type not found in registry: alloc::string::String"
}

// Or use ? operator
fn get_config() -> Result<std::sync::Arc<String>, singleton_registry::RegistryError> {
    app::get::<String>()
}

Note on Lock Poisoning: The registry automatically recovers from poisoned locks by extracting the inner value. This is safe because registry operations are idempotent.

Use Cases

  • Application singletons (Config, Logger, DatabasePool, etc.)
  • Isolated contexts (per-module registries, test isolation)
  • Function helpers and utility closures
  • Shared resources and components
  • Service locator pattern with type safety

Best Practice: Register all required types during initialization-time to ensure get() never fails during runtime.

Roadmap

v2.1.0 (Planned)

  • try_register() - Explicit error handling for registration operations
  • Enhanced documentation - Add "Limitations" section and comparison with alternatives
  • Benchmarks - Document performance characteristics
  • Lock poisoning tests - Verify recovery behavior under stress

Future Considerations

  • get_or_default() - Convenience method with fallback values
  • get_or_init() - Lazy initialization support
  • register_if_absent() - Conditional registration

Non-Goals

  • Async support (keeping it synchronous by design)
  • Removal operations (override-only by design)

See CHANGELOG.md for version history and CONTRIBUTING.md for contribution guidelines.

Installation

Add this to your Cargo.toml:

[dependencies]
singleton-registry = "2.0"

License

BSD-3-Clause

Commit count: 0

cargo fmt