[][src]Crate shaku

Shaku is a dependency injection library. It can be used directly or through integration with application frameworks such as Rocket (see shaku_rocket).

Getting started

Note: This getting started guide focuses on components, which live for the lifetime of the application (or, technically, the container). After reading this getting started guide, check out the provider module to learn how to create services with shorter lifetimes.

Structure your application

Start with your application's structs and traits. Use Arc<dyn T> for dependencies.

use std::sync::Arc;

trait IOutput {
    fn write(&self, content: String);
}

trait IDateWriter {
    fn write_date(&self);
}

struct ConsoleOutput;

impl IOutput for ConsoleOutput {
    fn write(&self, content: String) {
        println!("{}", content);
    }
}

struct TodayWriter {
    output: Arc<dyn IOutput>,
    today: String,
    year: usize,
}

impl IDateWriter for TodayWriter {
    fn write_date(&self) {
        self.output.write(format!("Today is {}, {}", self.today, self.year));
    }
}

Inherit "Interface" for the interface traits

Interface traits require certain bounds, such as 'static and optionally Send + Sync if using the thread_safe feature. The Interface trait acts as a trait alias for these bounds, and is automatically implemented on types which implement the bounds.

In our example, the two interface traits would become:

use shaku::Interface;

trait IOutput: Interface {
    fn write(&self, content: String);
}

trait IDateWriter: Interface {
    fn write_date(&self);
}

Mark structs as Component

A component is a struct that implements an interface trait. In our example, we have 2 components:

  • TodayWriter of type IDateWriter
  • ConsoleOutput of type IOutput

These components must implement Component, which can either be done manually or through a derive macro (using the derive feature):

use shaku::Component;

#[derive(Component)]
#[shaku(interface = IOutput)]
struct ConsoleOutput;

Express dependencies

Components can depend on other components. In our example, TodayWriter requires an IOutput component.

To express this dependency, first make sure the property is declared as a trait object wrapped in an Arc. Then (when using the derive macro) use the #[shaku(inject)] attribute on the property to tell shaku to inject the dependency.

In our example:

use shaku::Component;

#[derive(Component)]
#[shaku(interface = IDateWriter)]
struct TodayWriter {
    #[shaku(inject)]
    output: Arc<dyn IOutput>,
    today: String,
    year: usize,
}

If you don't use the derive macro, return Dependency objects in Component::dependencies and inject them manually in Component::build.

Register components

At application startup, create a ContainerBuilder and register your components with it. It will create a Container which you can use to resolve components.

use shaku::ContainerBuilder;

let mut builder = ContainerBuilder::new();
builder.register_type::<ConsoleOutput>();

let container = builder.build().unwrap();

Passing parameters

In many cases you need to pass parameters to a component. This can be done when registering a component into a ContainerBuilder.

You can register parameters either using their property name or their property type. In the latter case, you need to ensure that the type is unique.

Passing parameters is done using with_named_parameter and with_typed_parameter:

builder
    .register_type::<TodayWriter>()
    .with_named_parameter("today", "Jan 26".to_string())
    .with_typed_parameter::<usize>(2020);

Resolve components

During application execution, you’ll need to make use of the components you registered. You do this by resolving them from a Container with one of resolve methods.

Here's how we can print the date in our exmaple:

let writer: &dyn IDateWriter = container.resolve_ref().unwrap();
writer.write_date();

Now when you run your program...

  • The components and their parameters will be registered in the ContainerBuilder.
  • builder.build() will create the registered components in order of dependency (first ConsoleOutput, then TodayWriter). These components will be stored in the Container.
  • The resolve_ref() method asks the Container for an IDateWriter.
  • The Container sees that IDateWriter maps to TodayWriter, and it returns the component.

Later, if we wanted our application to write output in a different way, we would just have to implement a different IOutput and then change the registration at app startup. We won’t have to change any other code. Yay, inversion of control!

The full example

use shaku::{Component, ContainerBuilder, Interface};
use std::sync::Arc;

trait IOutput: Interface {
    fn write(&self, content: String);
}

trait IDateWriter: Interface {
    fn write_date(&self);
}

#[derive(Component)]
#[shaku(interface = IOutput)]
struct ConsoleOutput;

impl IOutput for ConsoleOutput {
    fn write(&self, content: String) {
        println!("{}", content);
    }
}

#[derive(Component)]
#[shaku(interface = IDateWriter)]
struct TodayWriter {
    #[shaku(inject)]
    output: Arc<dyn IOutput>,
    today: String,
    year: usize,
}

impl IDateWriter for TodayWriter {
    fn write_date(&self) {
        self.output.write(format!("Today is {}, {}", self.today, self.year));
    }
}

let mut builder = ContainerBuilder::new();
builder.register_type::<ConsoleOutput>();
builder
    .register_type::<TodayWriter>()
    .with_named_parameter("today", "Jan 26".to_string())
    .with_typed_parameter::<usize>(2020);
let container = builder.build().unwrap();

let writer: &dyn IDateWriter = container.resolve_ref().unwrap();
writer.write_date();

Crate features

By default shaku is thread-safe and exposes derive macros, but these can be disabled by opting out of the following features:

  • thread_safe: Requires components to be Send + Sync and provided services to be Send
  • derive: Uses the shaku_derive crate to provide proc-macro derives of Component and Provider.

Re-exports

pub use crate::component::Component;
pub use crate::component::Interface;
pub use crate::provider::ProvidedInterface;
pub use crate::provider::Provider;

Modules

component

This module contains trait definitions for components and interfaces

container

This module handles registering, building, and resolving services.

parameter

This module handles storing component parameters when registering and building components.

provider

This module contains trait definitions for provided services and interfaces

Structs

Container

Resolves services registered during the build phase.

ContainerBuildContext

Holds registration data, providers, and resolved components while building a Container. This struct is used during Component::build.

ContainerBuilder

Registers components in order to build a Container.

Dependency

Represents a service dependency. Notably, Dependency is used to determine the build order of components (build only after dependencies are built).

Enums

Error

Type Definitions

Result

Alias for a Result with the error type shaku::Error

Derive Macros

Component
Provider