You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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-trait crate支持,需在Cargo.toml中添加async-trait = "0.1"。
  • trait必须包含Sync + Send约束,因为Arc要求内部类型线程安全,异步操作通常需要跨线程执行。
  • trait对象是动态分发,会带来轻微性能开销,但多数应用场景可忽略。

内容的提问来源于stack exchange,提问作者Fred Hors

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 14:42:45