Expand description
Runtime dependency injection.
Documentation is under construction!
Examples
Basic dependency resolution
As a user of type A
we only care about getting an instance to use - the
life-cycle of A
and its dependencies remain hidden from us.
use dill::*;
use std::sync::Arc;
#[component]
struct A {
b: Arc<B>, // An instance of `B` will be resolved and injected when requesting `A`
}
impl A {
fn foo(&self) -> String {
format!("a::{}", self.b.bar())
}
}
#[component]
struct B;
impl B {
fn bar(&self) -> String {
format!("b")
}
}
let catalog = CatalogBuilder::new()
.add::<A>()
.add::<B>()
.build();
let a = catalog.get_one::<A>().unwrap();
assert_eq!(a.foo(), "a::b");
Using trait objects (aka Interfaces)
Every type can be associated with multiple traits that it implements using
CatalogBuilder::bind()
method, allowing dynamically picking best
implementation to use (e.g. based on config) or even using multiple
implementations at once (e.g. plugins).
use dill::*;
use std::sync::Arc;
// An interface that has two implementations below
trait A: Send + Sync {
fn foo(&self) -> String;
}
#[component]
struct AImpl1;
impl A for AImpl1 {
fn foo(&self) -> String {
format!("aimpl1")
}
}
#[component]
struct AImpl2;
impl A for AImpl2 {
fn foo(&self) -> String {
format!("aimpl2")
}
}
let catalog = CatalogBuilder::new()
.add::<AImpl1>()
.bind::<dyn A, AImpl1>()
.add::<AImpl2>()
.bind::<dyn A, AImpl2>()
.build();
// AllOf<T> is a DependencySpec that returns instances of all types that implement trait T
let ays = catalog.get::<AllOf<dyn A>>().unwrap();
let mut foos: Vec<_> = ays.iter().map(|a| a.foo()).collect();
foos.sort(); // Order is undefined
assert_eq!(foos, vec!["aimpl1".to_owned(), "aimpl2".to_owned()]);
Controlling lifetimes with Scopes
The life-cycle of a type is no longer controlled by the user of a type.
Author of type A
below can choose whether A
should be created per call
(Transient
) or reused by all clients (Singleton
).
use dill::*;
#[component]
#[scope(Singleton)]
struct A {
// Needed for compiler not to optimize type out
name: String,
}
impl A {
fn test(&self) -> String {
format!("a::{}", self.name)
}
}
let cat = CatalogBuilder::new()
.add::<A>()
.add_value("foo".to_owned())
.build();
let inst1 = cat.get::<OneOf<A>>().unwrap();
let inst2 = cat.get::<OneOf<A>>().unwrap();
// Expecting Singleton scope to return same instance
assert_eq!(
inst1.as_ref() as *const A,
inst2.as_ref() as *const A
);
Parametrizing builders
Builders can be parametrized during the registration process for convenience (e.g. with values read from configuration).
use dill::*;
#[component]
#[scope(Singleton)]
struct ConnectionPool {
host: String,
port: i32,
}
impl ConnectionPool {
fn url(&self) -> String {
format!("http://{}:{}", self.host, self.port)
}
}
let cat = CatalogBuilder::new()
.add_builder(
ConnectionPool::builder()
.with_host("foo".to_owned())
.with_port(8080),
)
.build();
let inst = cat.get::<OneOf<ConnectionPool>>().unwrap();
assert_eq!(inst.url(), "http://foo:8080");
Structs
- Builds all instances that implement a specific interface, returning a
Vec
. - Returns
None
if an optional dependency is not registered - Builds a single instance of type implementing specific interface. Will return an error if no implementations or multiple implementations were found.
- Caches an instance upon first creation for the entire duration of the program.
- Never caches so that every dependency resolution will result in a new instance.
- Takes a dynamic
Builder
and casts the instance to desired interface
Enums
Traits
- Builders are responsible for resolving dependencies and creating new instances of a certain type. Builders typically create new instances for every call, delegating the lifetime management to Scopes,
- Allows CatalogBuilder::add() to accept types with associated builder
- Specifies a particular way of resolving a dependency using the
Catalog
- Controls the lifetime of an instance created by
Builders