如何在Rust Diesel项目中传递任意表作为工具函数参数?
实现Diesel中通用的按ID查询工具函数
针对你的需求,我们可以通过关联类型+Diesel的trait约束来实现这个通用的按ID查询逻辑,让internal_get_by_id能适配任意带Uuid类型ID字段的表。下面是具体的实现步骤和代码:
第一步:明确依赖和基础类型
首先确保你已经导入了Diesel的核心模块,以及uuid相关依赖。假设你的项目使用PostgreSQL(如果是其他数据库,只需调整对应的Queryable约束中的数据库类型):
use diesel::prelude::*; use uuid::Uuid; // 假设你的自定义错误类型和连接包装器如下(根据实际项目调整) #[derive(Debug)] pub enum Error { DieselError(diesel::result::Error), // 其他错误类型... } impl From<diesel::result::Error> for Error { fn from(err: diesel::result::Error) -> Self { Error::DieselError(err) } } pub struct Conn { inner: PgConnection, } impl Conn { pub fn raw(&self) -> &PgConnection { &self.inner } } type Fallible<T> = Result<T, Error>;
第二步:修改Search trait,添加关联类型和约束
我们通过关联类型Table和IdColumn来绑定每个实体对应的表和ID字段,同时添加Diesel所需的trait约束,确保查询逻辑能正常编译:
pub trait Search: Sized + Queryable<<Self::Table as Table>::SqlType, Pg> { // 关联类型:当前实体对应的Diesel表 type Table: Table + Query; // 关联类型:当前表的Uuid类型ID字段 type IdColumn: Column + Expression<SqlType = Uuid> + Eq<Uuid>; fn internal_get_by_id( diesel_table: Self::Table, table_id: Self::IdColumn, conn: &Conn, id: Uuid, ) -> Fallible<Option<Self>> { diesel_table .filter(table_id.eq(id)) .first(conn.raw()) .optional() .map_err(Error::from) } // 每个实体需要实现的公开方法 fn get_by_id(conn: &Conn, id: Uuid) -> Fallible<Option<Self>>; }
约束说明:
Sized:确保我们可以返回Option<Self>(需要知道类型大小)Queryable<...>:确保当前实体可以从对应表的查询结果中自动映射(Diesel的自动派生功能会帮我们实现这个trait)Table + Query:确保关联的Table是Diesel可查询的表类型Column + Expression<SqlType = Uuid> + Eq<Uuid>:确保ID字段是表的有效列,类型为Uuid,并且支持eq比较操作
第三步:为User和Item实现Search trait
假设你的User和Item结构体已经通过Diesel的#[derive(Queryable)]派生了对应的映射逻辑,现在只需实现Search trait并指定关联类型:
// 从schema.rs自动生成的结构体(通常Diesel会帮你生成,这里手动示例) #[derive(Queryable)] pub struct User { pub id: Uuid, pub name: String, } #[derive(Queryable)] pub struct Item { pub id: Uuid, pub name: String, } impl Search for User { type Table = users::table; type IdColumn = users::id; fn get_by_id(conn: &Conn, id: Uuid) -> Fallible<Option<User>> { Self::internal_get_by_id(users::table, users::id, conn, id) } } impl Search for Item { type Table = items::table; type IdColumn = items::id; fn get_by_id(conn: &Conn, id: Uuid) -> Fallible<Option<Item>> { Self::internal_get_by_id(items::table, items::id, conn, id) } }
替代方案:使用方法泛型(无需关联类型)
如果你不想用关联类型,也可以直接在internal_get_by_id方法上添加泛型约束,这样trait定义会更灵活,但约束会更冗长:
pub trait Search: Sized + QueryableByName { fn internal_get_by_id<T, I>( diesel_table: T, table_id: I, conn: &Conn, id: Uuid, ) -> Fallible<Option<Self>> where T: Table + Query, I: Column + Expression<SqlType = Uuid> + IntoSql<Uuid>, T::Query: FilterDsl<I::Eq<Uuid>>, <T::Query as FilterDsl<I::Eq<Uuid>>>::Output: LoadQuery<&'_ PgConnection, Self>, { diesel_table .filter(table_id.eq(id)) .first(conn.raw()) .optional() .map_err(Error::from) } fn get_by_id(conn: &Conn, id: Uuid) -> Fallible<Option<Self>>; }
这种方式下,User和Item的实现和之前完全一致,不需要指定关联类型,但编译期的约束检查会更复杂。
总结
推荐使用关联类型的实现方式,代码结构更清晰,可读性更强,也更符合Diesel的设计习惯。这样你就可以轻松扩展到其他带Uuid类型ID的表,只需实现Search trait并指定对应的表和ID字段即可。
内容的提问来源于stack exchange,提问作者Pat
相关产品推荐
相关产品推荐

