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

Rust中基于Diesel实现通用CRUD泛型方案的技术咨询

Rust Diesel 泛型持久化层实现问题解答

方案可行性判断

这个泛型trait抽离默认CRUD实现的思路完全可行,不存在原则性问题,也不算过度设计——只要你项目里的关系型模型CRUD逻辑确实高度雷同,这个抽象的维护收益远大于实现成本,我自己在多个生产级Diesel项目里用过同类实现,只要控制好抽象边界,比到处复制粘贴样板代码的维护效率高很多。
Diesel本身的强类型设计天生支持这类泛型抽象,所有表、列、模型的约束都可以在编译期校验,不会引入任何运行时开销。实际落地的时候注意两个点就行:

  • 不要为了抽象强行兼容所有特殊场景,先覆盖80%的通用CRUD逻辑,个别有自定义查询需求的模型直接重写对应trait方法即可,不用硬把特殊逻辑塞进通用实现里把trait搞的无比复杂。
  • 如果你的关系型模型总数少于3个,抽这个trait的收益不高,可以等模型量上来、重复代码确实已经成为维护负担的时候再做,不用提前过度抽象。

get_table与get_id_column的类型定义

你在Diesel文档里找不到对应统一返回类型是正常的:Diesel中每张表、每一列都是独立的零大小类型,没有公共的具体类型可以直接作为返回值。如果一定要写方法返回值,可以用impl Trait写法,但这种方式很难在泛型场景下补全必要的 trait 约束,编译器错误提示也非常不友好。更符合Rust惯例的做法是在trait中定义关联类型,配合Diesel内置的trait做约束。

基于Diesel 2.x版本的通用实现参考如下:

use anyhow::Result;
use diesel::backend::Backend;
use diesel::prelude::*;
use diesel::query_builder::AsQuery;

// 按实际使用的后端替换连接类型,比如SqliteConnection/MysqlConnection
pub type DbConnection = diesel::PgConnection;

pub trait RelationalPersistence<Model, DB: Backend>: Persistence<Model> {
    /// 模型对应的数据表类型
    type Table: Table + AsQuery;
    /// 数据表的ID列类型,必须关联到上面定义的Table
    type IdColumn: Column<Table = Self::Table> + ExpressionMethods;

    /// 获取表实例,所有Diesel表/列都是零大小类型,提供默认实现即可,无需手动编写
    fn table() -> Self::Table {
        Self::Table::default()
    }

    /// 获取ID列实例
    fn id_col() -> Self::IdColumn {
        Self::IdColumn::default()
    }

    /// 通用create默认实现
    fn create(&self, model: &Model) -> Result<String>
    where
        // 约束模型可以插入到对应表
        Model: Insertable<Self::Table>,
        // 约束ID列可以作为返回字段,查询结果能反序列化为String
        Self::IdColumn: SelectableExpression<Self::Table> + ValidGrouping<()>,
        // 约束最终生成的insert语句可以在当前连接上执行,返回String类型结果
        diesel::dsl::Returning<
            diesel::insert_statement::InsertStatement<Self::Table, <Model as Insertable<Self::Table>>::Values>,
            Self::IdColumn
        >: LoadQuery<'static, DbConnection, String>,
    {
        let mut conn = crate::db::get_connection()?;
        let new_id = diesel::insert_into(Self::table())
            .values(model)
            .returning(Self::id_col())
            .get_result::<String>(&mut conn)?;
        Ok(new_id)
    }

    // 其余CRUD方法(get_by_id、list、update、delete等)都可以用同样的方式加约束写默认实现
}

具体模型的持久化实现会非常简洁,只需要指定两个关联类型即可,不需要写任何重复的CRUD逻辑:

use crate::schema::workspaces::{self, dsl::*};
use diesel::pg::Pg;

struct WorkspaceRelationalPersistence;

// 只需要指定关联的表和ID列,通用方法直接用默认实现
impl RelationalPersistence<Workspace, Pg> for WorkspaceRelationalPersistence {
    type Table = workspaces::table;
    type IdColumn = workspaces::id;
}

// 空impl即可满足上层Persistence trait的要求
impl Persistence<Workspace> for WorkspaceRelationalPersistence {}

几个落地的时候容易踩的坑:

  • 表和列都是零大小类型,和持久化结构体实例没有关联,不要用&self去调用对应的获取方法,直接用关联函数Self::table()即可,避免不必要的生命周期问题。
  • 每个通用方法单独加where约束,不要把所有约束都堆到trait定义层,否则实现trait的时候会被不必要的约束卡住,比如某个模型不需要delete功能,就不用为了满足trait约束强行实现对应的逻辑。
  • 如果你的模型ID不是String类型(比如自增i32、UUID等),可以把ID类型也做成关联类型,进一步提升通用性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 10:54:21