auto-di 0.2.1

Ergonomic async-aware automatic dependency injection for Rust
Documentation

auto-di

Automatic dependency injection for Rust, backed by a global inventory registry and async-safe initialization. The core API intentionally stays small: use #[singleton] for constructor injection and #[provider] for factory injection.

Components and constructor injection

use std::sync::Arc;
use auto_di::singleton;

trait Users: Send + Sync {}

#[singleton(name = "postgres", primary)]
fn users() -> Arc<dyn Users> {
    Arc::new(PostgresUsers)
}

#[singleton(eager, post_construct = "start", pre_destroy = "stop")]
impl UserService {
    fn new(#[qualifier("postgres")] users: Arc<dyn Users>) -> Self {
        Self { users }
    }

    async fn start(&self) {}
    async fn stop(&self) {}
}

#[singleton] supports these options:

  • name = "..."
  • primary
  • scope = "singleton" | "prototype" | "request"
  • eager
  • profile = "development"
  • condition = "ENV_KEY" or condition = "ENV_KEY=value"
  • post_construct = "async_method"
  • pre_destroy = "async_method"

Constructors accept Arc<T>, Option<Arc<T>>, Vec<Arc<T>>, Provider<T>, and Lazy<T>. Both sync and async constructors are supported.

Providers

Providers can be declared in any module:

#[provider]
fn database_pool() -> DatabasePool {
    DatabasePool::new()
}

Singletons automatically discover nested providers:

#[singleton]
impl UserService {
    fn new(repository: Arc<UserRepository>) -> Self {
        Self { repository }
    }

    #[provider]
    fn user_cache(&self) -> UserCache {
        UserCache::new()
    }
}
#[derive(Default)]
struct Beans;

#[configuration]
impl Beans {
    #[provider]
    fn config(&self) -> Config { Config::default() }

    #[provider(name = "main", primary)]
    async fn database(&self, config: Arc<Config>) -> Database {
        Database::connect(&config).await
    }
}

Environment properties

#[configuration_properties("database")]
struct DatabaseProperties {
    url: String,       // DATABASE_URL
    pool_size: usize,  // DATABASE_POOL_SIZE
}

Application and request scopes

#[application]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let service = auto_di::resolve::<UserService>().await?;
    Ok(())
}

let request = auto_di::global_container()?.request_context();
let dependency = request.resolve::<RequestDependency>().await?;

Active profiles come from comma-separated APP_PROFILES, or can be selected explicitly with Container::with_profiles(...).