FastSingletonCache

Struct FastSingletonCache 

Source
pub struct FastSingletonCache { /* private fields */ }
Expand description

Fast singleton cache using OnceCell for zero-overhead repeated access.

This cache provides near-zero overhead singleton resolution after the first access. It uses OnceCell internally to ensure thread-safe lazy initialization with optimal performance characteristics.

§Performance Characteristics

  • First access: Full factory execution + registration overhead
  • Subsequent access: Single atomic load (OnceCell optimizes to plain load)
  • Concurrent access: Lock-free reads, minimal contention on writes
  • Memory overhead: ~8 bytes per singleton + value size

§Sharding Strategy

The cache is sharded by TypeId hash to reduce contention when multiple threads are initializing different singletons concurrently. Each shard has its own lock.

§Examples

use ferrous_di::{ServiceCollection, Resolver};
use std::sync::Arc;

struct ExpensiveService {
    data: Vec<u8>,
}

impl ExpensiveService {
    fn new() -> Self {
        // Expensive initialization
        Self { data: vec![1, 2, 3, 4, 5] }
    }
}

// Register as singleton - automatically uses FastSingletonCache optimization
let mut services = ServiceCollection::new();
services.add_singleton(ExpensiveService::new());
let provider = services.build();

// First access - runs factory once, cached with OnceCell
let service1 = provider.get_required::<ExpensiveService>();

// Subsequent accesses - ultra-fast cached retrieval (~31ns)
let service2 = provider.get_required::<ExpensiveService>();
 
assert!(Arc::ptr_eq(&service1, &service2));

Implementations§

Source§

impl FastSingletonCache

Source

pub fn new() -> Self

Creates a new fast singleton cache.

Source

pub fn get_or_init<F>( &self, key: &Key, factory: F, ) -> Arc<dyn Any + Send + Sync>
where F: FnOnce() -> Arc<dyn Any + Send + Sync>,

Gets or initializes a singleton with the given factory.

This method provides optimal performance for repeated access to the same singleton. The factory is only called once, and subsequent calls return the cached value with minimal overhead.

§Performance Notes
  • Thread Safety: Multiple threads can safely call this concurrently
  • Initialization: Only one thread will execute the factory function
  • Subsequent Access: Near-zero overhead after initialization
§Examples
use ferrous_di::{ServiceCollection, Resolver};
use std::sync::Arc;

struct DatabaseService {
    connection_pool: Vec<String>,
}

impl DatabaseService {
    fn new() -> Self {
        // Expensive initialization
        Self {
            connection_pool: vec!["conn1".to_string(), "conn2".to_string()],
        }
    }
}

// The ServiceProvider automatically uses embedded OnceCell optimization for singletons
let mut services = ServiceCollection::new();
services.add_singleton_factory::<DatabaseService, _>(|_| DatabaseService::new());
let provider = services.build();

// First access - runs factory once
let db1 = provider.get_required::<DatabaseService>();

// Subsequent accesses - ultra-fast path (world-class 31ns performance)
for _ in 0..1000 {
    let db_same = provider.get_required::<DatabaseService>();
    assert!(Arc::ptr_eq(&db1, &db_same));
}
Source

pub fn get(&self, key: &Key) -> Option<Arc<dyn Any + Send + Sync>>

Gets an existing singleton without initializing.

Returns None if the singleton hasn’t been initialized yet. This is useful for checking if a singleton exists without triggering creation.

Source

pub fn clear(&self)

Clears all cached singletons.

This is primarily useful for testing scenarios where you need to reset the singleton state between tests.

§Warning

Clearing the cache while services are still in use can lead to multiple instances of what should be singletons. Use with caution.

Source

pub fn len(&self) -> usize

Returns the number of cached singletons.

Source

pub fn is_empty(&self) -> bool

Returns true if the cache is empty.

Trait Implementations§

Source§

impl Default for FastSingletonCache

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.