💉

【Rust】axum + shakuでDIコンテナを状態管理する際のCloneトレイト問題とArcによる解決

2024/12/15に公開

問題

axumでshakuのモジュールをStateとして使用する際、以下のようなエラーに遭遇することがある

the trait bound `AppModule: Clone` is not satisfied
the trait `Clone` is not implemented for `AppModule`

原因

この問題は以下の2つの要因から発生する:

  1. axumのRouterは状態(State)にCloneトレイトの実装を要求する
  2. shakuのmodule!マクロで生成されたモジュールには直接#[derive(Clone)]を付けることができない

解決方法

解決策として、shakuのモジュールをArcでラップする

registry
// 例)
module! {
    pub AppModule {
        components = [DoSomethingRepositoryImpl],
        providers = []
    }
}

impl AppModule {
    pub fn new(pool: ConnectionPool) -> Arc<Self> {
        Arc::new(
            AppModule::builder()
                .with_component_parameters::<DoSomethingRepositoryImpl>(
                    DoSomethingRepositoryImplParameters { db: pool }
                )
                .build()
        )
    }
}

pub type AppRegistry = Arc<AppModule>;

これが有効な理由:

  • ArcはCloneトレイトを実装している
  • ArcのCloneは参照カウントのインクリメントのみを行い、データの実際のコピーは行わない
  • ArcはSend + Syncも実装しているため、axumの非同期マルチスレッド環境での要件も満たす

実装例

router
// ルーターの実装例)
pub fn build_example_routers() -> Router<AppRegistry> {
    let routers = Router::new()
        .route("/example", get(do_something));
    Router::new().nest("/any", routers)
}

注意点

  • RcもCloneを実装しているが、Send + Syncを実装していないため、axumでは使用できない
  • shakuのモジュールに直接#[derive(Clone)]を付けることはできないため、Arcによるラッピングが必要

まとめ

axumでshakuのモジュールを状態として使用する場合、ArcでラップすることでCloneトレイトの要件を満たし、かつスレッドセーフな実装を実現できる。

Discussion