[][src]Crate coi

Coi provides an easy to use dependency injection framework. Currently, this crate provides the following:

  • coi::Inject (trait) - a marker trait that indicates a trait or struct is injectable.
  • coi::Provide - a trait that indicates a struct is capable of providing a specific implementation of some injectable trait. This is generated for you if you use coi::Inject (derive), but can also be written manually.
  • coi::Container - a container to manage the lifetime of all dependencies. This is still in its early stages, and currently only supports objects that are recreated with each request to coi::Container::resolve.
  • coi::ContainerBuilder - a builder for the above container to simplify construction and guarantee immutability after construction.

How this crate works

For any trait you wish to abstract over, have it inherit the Inject trait. For structs, impl Inject for that struct, e.g.

trait Trait1: Inject {}

struct Struct1;

impl Inject for Struct1 {}

Then, in order to register the injectable item with the coi::ContainerBuilder, you also need a struct that impls Provide<Output = T> where T is your trait or struct. Provide exposes a provide fn that takes &self and &Container. When manually implementing Provide you must resolve all dependencies with container. Here's an example below:

trait Dependency: Inject {}

struct Impl1 {
    dependency: Arc<dyn Dependency>,

impl Impl1 {
    fn new(dependency: Arc<dyn Dependency>) -> Self {
        Self { dependency }

impl Inject for Impl1 {}

impl Trait1 for Impl1 {}

struct Trait1Provider;

impl Provide for Trait1Provider {
    type Output = dyn Trait1;

    async fn provide(&self, container: &mut Container) -> coi::Result<Arc<Self::Output>> {
        let dependency = container.resolve::<dyn Dependency>("dependency").await?;
        Ok(Arc::new(Impl1::new(dependency)) as Arc<dyn Trait1>)

The "dependency" above of course needs to be registered in order for the call to resolve to not error out:

struct DepImpl;

impl Dependency for DepImpl {}

impl Inject for DepImpl {}

struct DependencyProvider;

impl Provide for DependencyProvider {
    type Output = dyn Dependency;

    async fn provide(&self, _: &mut Container) -> coi::Result<Arc<Self::Output>> {
        Ok(Arc::new(DepImpl) as Arc<dyn Dependency>)

async move {
    let mut container = container! {
        trait1 => Trait1Provider,
        dependency => DependencyProvider,
    let trait1 = container.resolve::<dyn Trait1>("trait1").await;

In general, you usually won't want to write all of that. You would instead want to use the procedural macro (see example below). The detailed docs for that are at coi::Inject (derive)


use coi::{container, Inject};
use std::sync::Arc;

// Mark injectable traits by inheriting the `Inject` trait.
trait Trait1: Inject {
    fn describe(&self) -> &'static str;

// For structs that will provide the implementation of an injectable trait, derive `Inject`
// and specify which expr will be used to inject which trait. The method can be any path.
// The arguments for the method are derived from fields marked with the attribute `#[inject]`
// (See Impl2 below).
// Currently, only one trait can be provided, but this will likely be expanded on in future
// versions of this crate.
#[provides(dyn Trait1 with Impl1)]
struct Impl1;

// Don't forget to actually implement the trait ;).
impl Trait1 for Impl1 {
    fn describe(&self) -> &'static str {
        "I'm impl1!"

// Mark injectable traits by inheriting the `Inject` trait.
trait Trait2: Inject {
    fn deep_describe(&self) -> String;

// For structs that will provide the implementation of an injectable trait, derive `Inject`
// and specify which method will be used to inject which trait. The arguments for the method
// are derived from fields marked with the attribute `#[inject]`, so the parameter name must
// match a field name.
#[provides(dyn Trait2 with Impl2::new(trait1))]
struct Impl2 {
    // The name of the field is important! It must match the name that's registered in the
    // container when the container is being built! This is similar to the behavior of
    // dependency injection libraries in other languages.
    trait1: Arc<dyn Trait1>,

// Implement the provider method
impl Impl2 {
    // Note: The param name here doesn't actually matter.
    fn new(trait1: Arc<dyn Trait1>) -> Self {
        Self { trait1 }

// Again, don't forget to actually implement the trait ;).
impl Trait2 for Impl2 {
    fn deep_describe(&self) -> String {
        format!("I'm impl2! and I have {}", self.trait1.describe())

// You might note that Container::resolve is async. This is to allow any provider to be async
// and since we don't know from the resolution perspective whether any provider will need to be
// async, they all have to be. This might be configurable through feature flags in a future
// version of the library.
async move {
    // "Provider" structs are automatically generated through the `Inject` attribute. They
    // append `Provider` to the name of the struct that is being derive (make sure you don't
    // any structs with the same name or your code will fail to compile.
    // Reminder: Make sure you use the same key here as the field names of the structs that
    // require these impls.
    let mut container = container! {
        trait1 => Impl1Provider,
        trait2 => Impl2Provider,

    // Once the container is built, you can now resolve any particular instance by its key and
    // the trait it provides. This crate currently only supports `Arc<dyn Trait>`, but this may
    // be expanded in a future version of the crate.
    let trait2 = container
        // Note: Getting the key wrong will produce an error telling you which key in the
        // chain of dependencies caused the failure (future versions might provider a vec of
        // chain that lead to the failure). Getting the type wrong will only tell you which key
        // had the wrong type. This is because at runtime, we do not have any type information,
        // only unique ids (that change during each compilation).
        .resolve::<dyn Trait2>("trait2")
        .expect("Should exist");
    println!("Deep description: {}", trait2.deep_describe());


Compilation taking too long? Turn off features you're not using.

To not use the default, and e.g. only the "async" feature:

# Cargo.toml
coi = { version = "...", default-features = false, features = ["async"] }
  • default: derive-async - Procedural macros are re-exported, async is available.
  • derive - Procedural macros are re-exported, but none of the code provides async support.
  • async - Procedural macros are not re-exported, so all generated code must be written manually. Async support is available.
  • None - Procedural macros are not re-exported and none of the code is async


External traits

Want to inject a trait that's not marked Inject? There's a very simple solution! It works even if the intended trait is not part of your crate.

// other.rs
pub trait Trait {

// your_lib.rs
use coi::Inject;
use other::Trait;

// Just inherit the intended trait and `Inject` on a trait in your crate,
// and make sure to also impl both traits for the intended provider.
pub trait InjectableTrait : Trait + Inject {}

#[provides(pub dyn InjectableTrait with Impl{})]
struct Impl {

impl Trait for Impl {

impl InjectableTrait for Impl {}

Where are the factory registrations!?

If you're familiar with dependency injection in other languages, you might be used to factory registration where you can provide a method/closure/lambda/etc. during registration. Since the crate works off of the Provide trait, you would have to manually implement Provide for your factory method. This would also require you to manually retrieve your dependencies from the passed in Container as shown in the docs above.

Why can't I derive Inject when my struct contains a reference?

In order to store all of the resolved types, we have to use std::any::Any, which, unfortunately, has the restriction Any: 'static. This is because it's not yet known if there's a safe way to downcast to a type with a reference (See the comments in this tracking issue).



A macro to simplify building of Containers.



A struct that manages all injected types.


A builder used to construct a Container.


An intermediary struct used to construct a scoped container. See Container::scopable



Errors produced by this crate


Control when Container will call Provide::provide.



A marker trait for injectable traits and structs.


A trait to manage the construction of an injectable trait or struct.

Type Definitions


Type alias to Result<T, coi::Error>.

Attribute Macros


A re-export of the async_trait attribute from the async-trait crate. See async-trait.

Derive Macros


Generates an impl for Inject and also generates a "Provider" struct with its own Provide impl.