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
ArcandMutex - 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 define_registry;
use Arc;
define_registry!;
define_registry!;
register;
register;
let message: = get.unwrap;
let number: = get.unwrap;
assert_eq!;
assert_eq!;
Advanced Usage
use define_registry;
use Arc;
define_registry!;
set_trace_callback;
register;
register;
let multiply_by_two: fn = ;
register;
assert!;
let number: = get.unwrap;
let config: = get.unwrap;
let doubler: = get.unwrap;
let result = doubler;
assert_eq!;
assert_eq!;
assert_eq!;
Multiple Isolated Registries
use define_registry;
define_registry!;
define_registry!;
define_registry!;
register;
register;
register;
let db_conn = .unwrap;
let cache_conn = .unwrap;
API Reference
Each registry created with define_registry!(name) provides:
name::register(value)- Register a valuename::register_arc(arc_value)- Register an Arc-wrapped valuename::get::<T>()- Retrieve a value asArc<T>(returnsResult)name::get_cloned::<T>()- Retrieve a cloned value (requiresClone, returnsResult)name::contains::<T>()- Check if a type is registered (returnsResult)name::set_trace_callback(callback)- Set up tracingname::clear_trace_callback()- Clear tracing
Error Handling
All fallible operations return Result<T, RegistryError>:
Example:
use define_registry;
define_registry!;
// Handle errors explicitly
match
// Or use ? operator
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 valuesget_or_init()- Lazy initialization supportregister_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:
[]
= "2.0"
License
BSD-3-Clause