在Rust清洁架构项目中运行时切换DB实现的问题求助
问题:Rust清洁架构中运行时切换Repo导致类型不兼容
这是一个用Rust实现清洁架构的未完成项目,架构分为四层:
- entities层(也叫domain层)
- services层(也叫use cases层,存放业务逻辑)
- repositories层(也叫adapters层,存放数据库等驱动的具体实现)
- ports层(也叫controllers或presenters层,暂未实现,用main.rs替代)
在main.rs中尝试根据use_postgres变量在运行时切换使用postgres::Repo或in_memory::Repo时,Rust报错if与else分支类型不兼容,代码及错误信息如下:
// 单独使用任意一个都正常: // let db_repo = Arc::new(repositories::in_memory::Repo::new()); // let pg_pool = Arc::new(sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres").await.unwrap()); // let db_repo = Arc::new(repositories::postgres::Repo::new(pg_pool)); // 但这样写就报错: let db_repo = if use_postgres { let pg_pool = Arc::new(sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres").await.unwrap()); Arc::new(repositories::postgres::Repo::new(pg_pool)) } else { Arc::new(repositories::in_memory::Repo::new()) };
错误信息:
error[E0308]: `if` and `else` have incompatible types --> src\main.rs:37:9 | 28 | let db_repo = if use_postgres { | ___________________- 29 | | let pg_pool = Arc::new( 30 | | sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres") 31 | | .await ... | 35 | | Arc::new(repositories::postgres::Repo::new(pg_pool)) | | ---------------------------------------------------- expected because of this 36 | | } else { 37 | | Arc::new(repositories::in_memory::Repo::new()) | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `repositories::postgres::Repo`, found struct `in_memory::Repo` 38 | | }; | |_____- `if` and `else` have incompatible types | = note: struct `in_memory::Repo` and struct `repositories::postgres::Repo` have similar names, but are actually distinct types note: struct `in_memory::Repo` is defined in module `crate::repositories::in_memory` of the current crate --> src\repositories\in_memory\mod.rs:6:1 | 6 | pub struct Repo { | ^^^^^^^^^^^^^^^ note: struct `repositories::postgres::Repo` is defined in module `crate::repositories::postgres` of the current crate --> src\repositories\postgres\mod.rs:6:1 | 6 | pub struct Repo { | ^^^^^^^^^^^^^^^
解决方案:使用 trait 对象实现多态
Rust是静态类型语言,if/else分支必须返回相同类型。两个Repo是不同结构体,需要用trait对象统一类型,让它们实现同一个trait,再将Arc包裹的对象转为trait对象(即Arc<dyn TraitName>)。
步骤1:定义统一的Repository trait
在repositories模块下创建公共trait,比如TodoRepo,包含业务所需的所有方法:
// src/repositories/mod.rs use crate::entities::Todo; use async_trait::async_trait; // 需要async-trait crate支持异步方法 #[async_trait] pub trait TodoRepo: Sync + Send { async fn create(&self, todo: Todo) -> Result<Todo, Box<dyn std::error::Error>>; // 添加其他业务方法,比如get_all、update、delete等 }
步骤2:让两个Repo实现该trait
分别给in_memory::Repo和postgres::Repo实现TodoRepo trait:
// src/repositories/in_memory/mod.rs use super::TodoRepo; use crate::entities::Todo; use async_trait::async_trait; pub struct Repo { todos: Vec<Todo>, } impl Repo { pub fn new() -> Self { Self { todos: Vec::new() } } } #[async_trait] impl TodoRepo for Repo { async fn create(&self, todo: Todo) -> Result<Todo, Box<dyn std::error::Error>> { // 内存实现逻辑 Ok(todo) } }
// src/repositories/postgres/mod.rs use super::TodoRepo; use crate::entities::Todo; use async_trait::async_trait; use sqlx::PgPool; use std::sync::Arc; pub struct Repo { pool: Arc<PgPool>, } impl Repo { pub fn new(pool: Arc<PgPool>) -> Self { Self { pool } } } #[async_trait] impl TodoRepo for Repo { async fn create(&self, todo: Todo) -> Result<Todo, Box<dyn std::error::Error>> { // Postgres实现逻辑 Ok(todo) } }
步骤3:在main.rs中使用trait对象
修改main.rs代码,将db_repo的类型指定为Arc<dyn TodoRepo>,让两个分支的返回对象统一为该trait对象:
// src/main.rs use repositories::TodoRepo; use std::sync::Arc; #[tokio::main] async fn main() { let use_postgres = true; // 可根据配置动态切换 let db_repo: Arc<dyn TodoRepo> = if use_postgres { let pg_pool = Arc::new(sqlx::PgPool::connect("postgres://postgres:postgres@localhost:5432/postgres").await.unwrap()); Arc::new(repositories::postgres::Repo::new(pg_pool)) } else { Arc::new(repositories::in_memory::Repo::new()) }; // 统一调用trait中的方法 let todo = crate::entities::Todo::new("test".to_string()); let created = db_repo.create(todo).await.unwrap(); }
注意事项
- 异步trait需要
async-traitcrate支持,需在Cargo.toml中添加async-trait = "0.1"。 - trait必须包含
Sync + Send约束,因为Arc要求内部类型线程安全,异步操作通常需要跨线程执行。 - trait对象是动态分发,会带来轻微性能开销,但多数应用场景可忽略。
内容的提问来源于stack exchange,提问作者Fred Hors
相关产品推荐
相关产品推荐

