TL;DR

Rust の DI コンテナのクレート。依存関係が正しく解決できるかを、コンパイル時に確かめられる。

仕組み

shaku が提供するマクロで、半自動的に依存を解決する。使い方は次の 3 段階である。

  1. インタフェースとなるトレイトと、それを実装する構造体を定義し、#[derive(Component)] と #[shaku(...)] を付ける。注入したいフィールドには #[shaku(inject)] を付ける
  2. module! マクロで、DI コンテナ用の構造体(ここでは MyModule)を生成する
  3. DI コンテナ用の構造体から、必要なコンポーネントを取り出す

例

コンポーネントを定義する。DateLoggerImpl は Logger に依存している。

use shaku::{module, Component, Interface, HasComponent};
use std::sync::Arc;
 
trait Logger: Interface {
    fn log(&self, content: &str);
}
 
trait DateLogger: Interface {
    fn log_date(&self);
}
 
#[derive(Component)]
#[shaku(interface = Logger)]
struct LoggerImpl;
 
impl Logger for LoggerImpl {
    fn log(&self, content: &str) {
        println!("{}", content);
    }
}
 
#[derive(Component)]
#[shaku(interface = DateLogger)]
struct DateLoggerImpl {
    #[shaku(inject)]
    logger: Arc<dyn Logger>,
    today: String,
    year: usize,
}
 
impl DateLogger for DateLoggerImpl {
    fn log_date(&self) {
        self.logger.log(&format!("Today is {}, {}", self.today, self.year));
    }
}

module! マクロで DI コンテナ用の構造体を生成する。

module! {
    MyModule {
        components = [LoggerImpl, DateLoggerImpl],
        providers = []
    }
}

DI コンテナ用の構造体を経由して、コンポーネントを取り出す。

fn main() {
    let module = MyModule::builder()
        .with_component_parameters::<DateLoggerImpl>(DateLoggerImplParameters {
            today: "Jan 26".to_string(),
            year: 2020
        })
        .build();
 
    let date_logger: &dyn DateLogger = module.resolve_ref();
    date_logger.log_date();
}

注意点

axum 向けに shaku_axum というクレートも用意されている。これを使うと、ハンドラで必要な依存を簡単に取り出せる。Actix Web や Rocket 向けのクレートもある。

関連

出典