Crate dill

source · []
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(
        builder_for::<ConnectionPool>()
            .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.

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 both impl types with associated builder and custom builders

Specifies a particular way of resolving a dependency using the Catalog

Controls the lifetime of an instance created by Builders

Functions

Used to create an instance of a default builder for a component. This instance can be then be parametrized before adding it into the CatalogBuilder.

Attribute Macros