如何解决Rust中bin crate与lib crates的循环依赖问题?
解决Rust Crate循环依赖的可行方案
解决这个循环依赖的核心思路是把共享的服务抽象定义抽离到独立的lib crate,打破app与各业务lib之间的双向依赖链。具体步骤如下:
1. 新建共享抽象层Crate
创建一个独立的lib crate(比如命名为service-defs或shared),用来存放所有服务的抽象trait和公共数据类型,这个crate不依赖任何其他业务crate。
示例代码(crates/shared/src/lib.rs):
use async_trait::async_trait; use std::error::Error; // 玩家服务的抽象定义 #[async_trait] pub trait PlayerService: Send + Sync + 'static { async fn player_by_id(&self, id: u32) -> Result<Player, Box<dyn Error>>; } // 公共玩家数据结构 #[derive(Debug)] pub struct Player { pub name: String, } // 用户服务的抽象定义 #[async_trait] pub trait UserService: Send + Sync + 'static { async fn user_by_id(&self, id: u32) -> Result<User, Box<dyn Error>>; } // 公共用户数据结构 #[derive(Debug)] pub struct User { pub name: String, }
2. 修改业务Lib Crate(players/users)
让业务lib不再依赖app,转而依赖新建的共享crate,同时让自身的Service实现共享层的抽象trait。
示例代码(crates/players/src/lib.rs):
use async_graphql::{Context, Object, Result}; use shared::{Player, PlayerService}; use std::sync::Arc; #[derive(Default)] pub struct PlayersQueryRoot; #[Object] impl PlayersQueryRoot { async fn players_query_root(&self, ctx: &Context<'_>) -> Result<String> { // 从Context中获取抽象的PlayerService,而非app的具体Services结构体 let player_service = ctx.data_unchecked::<Arc<dyn PlayerService>>(); let player = player_service.player_by_id(1).await?; Ok(player.name) } } // 原有的玩家服务实现 pub struct Service; impl Service { pub fn new() -> Self { Service } } // 实现共享层的PlayerService trait #[async_trait::async_trait] impl PlayerService for Service { async fn player_by_id(&self, id: u32) -> Result<Player, Box<dyn Error>> { // 这里写原有的业务逻辑,比如模拟返回玩家数据 Ok(Player { name: format!("玩家{}", id), }) } }
3. 修改App Crate
让app同时依赖共享crate和业务lib,重新定义Services结构体为抽象trait对象的组合,彻底解除对业务lib具体类型的强依赖。
示例代码(app/src/main.rs):
use async_graphql::{EmptyMutation, EmptySubscription, Schema}; use shared::{PlayerService, UserService}; use std::sync::Arc; // 现在Services持有抽象trait对象,而非业务lib的具体类型 pub struct Services { pub players: Arc<dyn PlayerService>, pub users: Arc<dyn UserService>, } impl Services { pub async fn new() -> Self { Self { users: Arc::new(user::Service::new()), players: Arc::new(players::Service::new()), } } } // 创建GraphQL Schema时,注入所需的抽象服务 pub fn create_schema(services: Arc<Services>) -> GraphqlSchema { Schema::build(QueryRoot::default(), EmptyMutation, EmptySubscription) // 按需注入单个服务,或者注入整个Services结构体 .data(services.players.clone()) .data(services.users.clone()) .finish() }
4. 调整Cargo依赖配置
- app的Cargo.toml:
[dependencies] players = { path = "../crates/players" } users = { path = "../crates/users" } shared = { path = "../crates/shared" } async-graphql = "..." async-trait = "..."
- players/users的Cargo.toml:
[dependencies] shared = { path = "../crates/shared" } async-graphql = "..." async-trait = "..."
方案原理
通过共享抽象层,现在的依赖关系变成:app → 业务lib + shared业务lib → sharedshared 无任何业务依赖
彻底打破了原有的循环依赖链,同时保留了业务代码的模块化和复用性。
内容的提问来源于stack exchange,提问作者Fred Hors
相关产品推荐
相关产品推荐

