elemental-object 0.1.0

TypeId-based object pool for hotreloadable, cross-runtime shared state management.
Documentation

elemental-object

A type-safe, thread-safe global object pool for Rust applications.

Features

  • Type-Safe: Leverages Rust's type system to uniquely identify and retrieve objects
  • Lock-Free: Built on scc::HashMap for efficient concurrent access
  • Async-Friendly: Full support for async/await patterns
  • Lifecycle Management: Custom shutdown handlers for graceful cleanup
  • Wait-for-Initialization: Optional notification system for coordinating startup
  • Zero-Cost Abstractions: LazyLock ensures minimal overhead

Quick Start

Add to your Cargo.toml:

[dependencies]
elemental-object = "0.1"

Basic usage:

use elemental_object::{provide, require};

#[tokio::main]
async fn main() {
    // Store a value
    let config = MyConfig { 
        database_url: "postgres://localhost".to_string() 
    };
    provide(config, None).await;

    // Retrieve it anywhere in your app
    let config = require::<MyConfig>()?;
    println!("Database: {}", config.database_url);
    
    Ok(())
}

Common Patterns

1. Global Singleton Service

Store and access application services without passing them through every function:

use elemental_object::{provide, require};

struct DatabaseConnection {
    // ...
}

impl DatabaseConnection {
    async fn shutdown(&self) {
        // Cleanup code
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    let db = DatabaseConnection::new().await;
    
    // Register with shutdown handler
    provide(
        db,
        Some(|db| async move {
            db.shutdown().await;
        })
    ).await;

    // Access from anywhere
    let db = require::<DatabaseConnection>()?;
    // ... use db
    
    Ok(())
}

2. Wait-for-Initialization Pattern

Coordinate startup between multiple tasks:

use elemental_object::{provide, acquire};

#[tokio::main]
async fn main() {
    // Task 1: Initialize config from slow source
    tokio::spawn(async {
        tokio::time::sleep(Duration::from_secs(2)).await;
        let config = load_config_from_file().await;
        provide(config, None).await;
    });

    // Task 2: Wait until config is ready
    let config = acquire::<AppConfig>()?;  // Blocks until provided
    start_server(config).await;
}

3. Multiple Types in One Pool

Store different types without creating separate managers:

use elemental_object::{provide, require};

provide(my_db, None).await;
provide(my_cache, None).await;
provide(my_logger, None).await;

// Retrieve each by type
let db = require::<Database>()?;
let cache = require::<Cache>()?;
let logger = require::<Logger>()?;

4. Lifecycle with Cleanup

Ensure resources are properly cleaned up when replaced or removed:

use elemental_object::{provide, drop_value};

// Provide resource with shutdown handler
provide(
    resource,
    Some(|res| async move {
        println!("Cleaning up: {}", res.name);
        res.cleanup().await;
    })
).await;

// Later: explicitly remove (triggers cleanup)
drop_value::<MyResource>().await;

// Or replace with new value (triggers cleanup of old value)
provide(new_resource, None).await;

API Overview

Retrieving Values

Function Mode Behavior
require::<T>() Async Get value or return error
require_sync::<T>() Sync Get value synchronously or return error
acquire::<T>() Async Wait for value to be provided, then return it

Providing Values

Function Behavior
provide::<T>(value, shutdown) Store owned value with optional shutdown handler
provide_arc::<T>(arc_value, shutdown) Store Arc-wrapped value (avoids re-wrapping)
fulfill::<T>(value) Mark value as fulfilled and notify waiters (idempotent)
fulfill_arc::<T>(arc_value) Fulfill with Arc value

Cleanup

Function Behavior
drop_value::<T>() Remove value and call shutdown handler
drop_entry::<T>() Remove entire entry and call shutdown handler
shutdown() Gracefully shutdown all objects in pool

Features

Default (with notification)

The default configuration includes the "notify" feature, enabling wait-for-initialization patterns:

use elemental_object::acquire;

let config = acquire::<AppConfig>()?;  // Available

Minimal (without notification)

For applications that don't need async waiting, reduce dependencies:

[dependencies]
elemental-object = { version = "0.1", default-features = false }

This removes the Tokio dependency and disables acquire(), fulfill(), and wait_value().

Thread Safety

  • All operations are thread-safe and concurrent
  • Values are wrapped in Arc<T> for shared ownership
  • Built on lock-free scc::HashMap for efficient concurrent access
  • Supports both sync and async access patterns

Type Requirements

Values stored in the pool must satisfy:

T: Any + Send + Sync

This includes:

  • Most standard types (String, integers, collections)
  • Custom structs and enums
  • Trait objects (less common, as type identification becomes ambiguous)

Error Handling

When a value is not found in the pool:

use elemental_object::require;

match require::<Config>() {
    Ok(config) => { /* ... */ },
    Err(e) => eprintln!("Config not initialized: {}", e),
}

The error message includes the type name for debugging:

Cannot get object `my_app::config::Config` from pool

Examples

See the tests for more complete examples:

  • test_fulfill: Wait-for-initialization pattern
  • test_provide: Multiple types in pool

Run with:

cargo test --features notify

Performance

  • Retrieval: O(1) hash lookup
  • Insertion/Update: O(1) amortized
  • Shutdown: O(n) where n = number of types in pool
  • Memory: Minimal overhead (LazyLock initializes on first use)

Design Document

For detailed architectural information, see DESIGN.md.

Integration with Elemental

This crate is part of the Elemental framework and is commonly used in conjunction with:

  • elemental-core: Core framework utilities
  • elemental-shared: Shared configuration and persistence

License

Licensed under the same terms as the Elemental project. See LICENSE for details.

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.


For questions or issues, please open an issue on the GitHub repository.