Expand description
§Reinhardt Dependency Injection
FastAPI-inspired dependency injection system for Reinhardt.
§Features
- Type-safe: Full compile-time type checking
- Async-first: Built for async/await
- Scoped: Request-scoped and singleton dependencies
- Composable: Dependencies can depend on other dependencies
- Cache: Automatic caching within request scope
- Circular Dependency Detection: Automatic runtime detection with optimized performance
§Development Tools (dev-tools feature)
When the dev-tools feature is enabled, additional debugging and profiling tools are available:
- Visualization: Generate dependency graphs in DOT format for Graphviz
- Profiling: Track dependency resolution performance and identify bottlenecks
- Advanced Caching: LRU and TTL-based caching strategies
§Generator Support (generator feature) ✅
Generator-based dependency resolution for lazy, streaming dependency injection.
Note: Uses genawaiter crate as a workaround for unstable native async yield.
Will be migrated to native syntax when Rust stabilizes async generators.
// let gen = DependencyGenerator::new(|co| async move {
// let db = resolve_database().await;
// co.yield_(db).await;
//
// let cache = resolve_cache().await;
// co.yield_(cache).await;
// });§Example
// Define a dependency
// struct Database {
// pool: DbPool,
// }
//
// #[async_trait]
// impl Injectable for Database {
// async fn inject(ctx: &InjectionContext) -> Result<Self> {
// Ok(Database {
// pool: get_pool().await?,
// })
// }
// }
//
// Use in endpoint
// #[endpoint(GET "/users")]
// async fn list_users(
// db: Depends<Database>,
// ) -> Result<Vec<User>> {
// db.query("SELECT * FROM users").await
// }§InjectionContext Construction
InjectionContext is constructed using the builder pattern with a required singleton scope:
use reinhardt_di::{InjectionContext, SingletonScope};
use std::sync::Arc;
// Create singleton scope
let singleton = Arc::new(SingletonScope::new());
// Build injection context with singleton scope
let ctx = InjectionContext::builder(singleton).build();Optional request and param context can be added:
ⓘ
use reinhardt_di::{InjectionContext, SingletonScope};
use reinhardt_http::Request;
use std::sync::Arc;
let singleton = Arc::new(SingletonScope::new());
// Create a dummy request for demonstration
let request = Request::builder()
.method(hyper::Method::GET)
.uri("/")
.version(hyper::Version::HTTP_11)
.headers(hyper::HeaderMap::new())
.body(bytes::Bytes::new())
.build()
.unwrap();
let ctx = InjectionContext::builder(singleton)
.with_request(request)
.build();§Circular Dependency Detection
The DI system automatically detects circular dependencies at runtime using an optimized thread-local mechanism:
ⓘ
#[derive(Clone)]
struct ServiceA {
b: Arc<ServiceB>,
}
#[derive(Clone)]
struct ServiceB {
a: Arc<ServiceA>, // Circular dependency!
}
#[async_trait]
impl Injectable for ServiceA {
async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
let b = ctx.resolve::<ServiceB>().await?;
Ok(ServiceA { b })
}
}
#[async_trait]
impl Injectable for ServiceB {
async fn inject(ctx: &InjectionContext) -> DiResult<Self> {
let a = ctx.resolve::<ServiceA>().await?;
Ok(ServiceB { a })
}
}
let singleton = Arc::new(SingletonScope::new());
let ctx = InjectionContext::builder(singleton).build();
// This will return Err with DiError::CircularDependency
let result = ctx.resolve::<ServiceA>().await;
assert!(result.is_err());§Performance Characteristics
- Cache Hit: < 5% overhead (cycle detection completely skipped)
- Cache Miss: 10-20% overhead (O(1) detection using HashSet)
- Deep Chains: Sampling reduces linear cost (checks every 10th at depth 50+)
- Thread Safety: Thread-local storage eliminates lock contention
§Development Tools Example
ⓘ
// fn visualize_dependencies() {
// let mut graph = DependencyGraph::new();
// graph.add_node("Database", "singleton");
// graph.add_node("UserService", "request");
// graph.add_dependency("UserService", "Database");
//
// println!("{}", graph.to_dot());
// }
//
// fn profile_resolution() {
// let mut profiler = DependencyProfiler::new();
// profiler.start_resolve("Database");
// // ... perform resolution ...
// profiler.end_resolve("Database");
//
// let report = profiler.generate_report();
// println!("{}", report.to_string());
// }Re-exports§
pub use context::InjectionContext;pub use context::InjectionContextBuilder;pub use context::RequestContext;pub use cycle_detection::CycleError;pub use cycle_detection::ResolutionGuard;pub use cycle_detection::begin_resolution;pub use cycle_detection::register_type_name;pub use cycle_detection::with_cycle_detection_scope;pub use function_handle::FunctionHandle;pub use override_registry::OverrideRegistry;pub use depends::Depends;pub use depends::DependsBuilder;pub use injectable::Injectable;pub use injected::DependencyScope as InjectedScope;pub use injected::Injected;pub use injected::InjectionMetadata;pub use injected::OptionalInjected;pub use provider::Provider;pub use provider::ProviderFn;pub use registry::DependencyRegistration;pub use registry::DependencyRegistry;pub use registry::DependencyScope;pub use registry::FactoryTrait;pub use registry::global_registry;pub use scope::RequestScope;pub use scope::Scope;pub use scope::SingletonScope;pub use inventory;
Modules§
- context
- Injection context for dependency resolution
- cycle_
detection - Task-local circular dependency detection mechanism
- depends
- Depends wrapper for dependency injection
- function_
handle - Function handle for fluent override API
- graph
- Dependency graph analysis and visualization
- injectable
- Injectable trait for dependencies
- injected
- Injected wrapper for dependency injection
- override_
registry - Override registry for dependency injection
- params
- Reinhardt Parameter Extraction
- provider
- Dependency providers
- registry
- Global dependency registry for FastAPI-style dependency injection
- scope
- Dependency scopes
Macros§
- submit_
registration - Helper macro for submitting registrations to inventory