Example
/////////////////////////////////////////
// Define interfaces in traits
// Implement traits to define components
/////////////////////////////////////////
;
/////////////////////////////////////////
// Register components
let cat = builder
.
.
.build;
// Get objects and have their deps satisfied automatically
let inst = cat..unwrap;
assert_eq!;
Status
While documentation is still lacking, this crate is production-ready and is in active use in kamu-cli - a fairly large project organized according to Onion/Clean Architecture.
Features
- Injection specs:
OneOf- expects a single implementation of a given interfaceAllOf- returns a collection of all implementations on a given interfaceMaybe<Spec>- returnsNoneif innerSpeccannot be resolvedLazy<Spec>- injects an object that delays the creation of value until it is requested
- Component scopes:
Transient(default) - short-lived, a new instance is created for every invocationAgnostic- same asTransientbut signals that it's OK to inject this instance into more long-lived scopesSingleton- an instance is created upon first use and then reused for the rest of callsTransaction- an instance will be cached for the duration of a transaction
#[component]macro can deriveBuilder:- When used directly for a
structor onimplblock withImpl::new()function - Can inject as
Arc<T>,T: Clone,&T Option<T>is interpreted asMaybe<OneOf<T>>specVec<T>is interpreted asAllOf<T>spec- Supports custom argument bindings in
Builder - Supports default interface bindings via
#[interface]attribute - Supports metadata association via
#[meta(...)]attribute
- When used directly for a
- Prebuilt / add by value support
- By value injection of
Clonetypes Catalogcan be self-injected- Chaining of
Catalogs allows adding values dynamically (e.g. in middleware chains liketower) CatalogBuilder::validate()performs static analysis to detect dangling and ambiguous dependencies and scope inversion issuesCatalogcan be scoped within atokiotask as "current" to override the source ofLazyly injected values- Utils:
dill::utils::graphvizanddill::utils::plantumlallow visualizing the dependency graph
Design Principles
- Non-intrusive
- Writing DI-friendly code should be as close as possible to writing regular types
- DI should be an additive feature - we should be able to disable it and have all code compile (i.e. allowing for DI-optional libraries)
- Externalizable
- It should be possible to add DI capabilities to 3rd party code
- Focus on runtime injection
- Leveraging type system and zero-cost abstractions is great, but hard to get right - this project started because we needed something practical fast
- Some cases involve dynamic registration of objects (e.g. adding an auth token during HTTP request processing), which further complicates compile-time DI
- We use DI to integrate coarse-grained components, where some overhead is tolerable
- We compensate for safety by providing runtime graph validation
- Focus on constructor injection
- Field/property/accessor-based injection would complicate the system, and in our experience is of little use
- Put implementation in control
- The type implementor (and not type user) usually has the best knowledge of what the optimal lifecycle for the type should be and its concurrency characteristics, thus implementors should be in control of the defaults
TODO
- Support
stablerust - Make
Scopes external toBuilders so they could be overridden - Consider using traits to map
Arc,Option,Vecto dependency specs instead of relying on macro magic - Add
trybuildtests (see https://youtu.be/geovSK3wMB8?t=956) - Support generic types
- Replace
add_*with genericadd<B: Into<Builder>> - value by reference in
new() - doctests
- proc macro error handling
- build a type without registering
- Support PImpl idiom, where
Arc<dyn Iface>can be hidden behind a movable object- This even further hides lifetime management from consumers
- Allows generic methods to be implemented to improve usability of
dyn Trait(e.g. acceptingimpl AsRef<str>parameters instead of&str)