能否在Rust trait默认实现中调用Diesel ORM函数?
问题
我想在Rust项目中消除重复代码。现有一个Table trait,其中包含大量可通用实现的函数,这里以count函数为例:
pub trait Table { fn count(&self, env: &mut dyn Environment) -> Result<i64, Error>; }
我有两个结构体FooTable和BarTable都实现了Table trait(实际项目中还有更多),它们的count实现代码几乎完全一样:
pub struct FooTable { pub table: foos, } impl Table for FooTable { fn count(&self, env: &mut dyn Environment) -> Result<i64, Error> { Ok(self.table.count().first::<i64>(env.db().connection())?) } } pub struct BarTable { pub table: bars, } impl Table for BarTable { fn count(&self, env: &mut dyn Environment) -> Result<i64, Error> { Ok(self.table.count().first::<i64>(env.db().connection())?) } }
只要能统一访问FooTable::table和BarTable::table,count的实现就能通用。于是我尝试用带默认count实现的GenericTable trait重构,但遇到了关联类型相关约束的问题:
pub trait GenericTable { type TableType: diesel::Table + ... // 这里需要添加其他必要约束 fn get_table(&mut self) -> &mut Self::TableType; fn count(&self, env: &mut dyn Environment) -> Result<i64, Error> { Ok(self.get_table().count().first::<i64>(env.db().connection())?) } } impl GenericTable for FooTable { type TableType = foos; fn get_table(&mut self) -> &mut Self::TableType { &mut self.table } } impl GenericTable for BarTable { type TableType = bars; fn get_table(&mut self) -> &mut Self::TableType { &mut self.table } }
编译时出现错误:
Ok(self.get_table().count().first::<i64>(env.db().connection())?) | ^^^^^ the trait `diesel::Table` is not implemented for `<<Self as GenericTable>::TableType as AsQuery>::Query`
我想知道能不能通过关联类型为<<Self as GenericTable>::TableType as AsQuery>::Query添加约束?另外,我不想给count本身加泛型参数,那样会破坏多态性——我需要能调用任何实现GenericTable的结构体的count方法。
解决方案
要解决这个问题,你需要在GenericTable trait中对TableType的关联类型添加明确约束,核心是限定AsQuery的关联类型Query满足diesel::Table以及count方法依赖的其他Diesel trait(比如QueryFragment、QueryId等)。
方案1:在trait定义中添加where约束
直接在GenericTable的定义里,通过where子句约束TableType的关联类型:
use diesel::prelude::*; pub trait GenericTable { type TableType; // 注意:count是&self调用,get_table不需要可变引用,改成&self返回不可变引用 fn get_table(&self) -> &Self::TableType; fn count(&self, env: &mut dyn Environment) -> Result<i64, Error> where // 约束TableType能转为查询 Self::TableType: AsQuery, // 约束转换后的Query满足Diesel表查询的必要条件 <Self::TableType as AsQuery>::Query: diesel::Table + QueryFragment<diesel::pg::Pg> // 根据你的数据库类型调整,比如Pg/Sqlite/Mysql + QueryId, // 确保count查询返回单值 <<Self::TableType as AsQuery>::Query as diesel::Table>::SqlType: diesel::sql_types::SingleValue, { Ok(self.get_table().count().first::<i64>(env.db().connection())?) } }
方案2:为关联类型直接添加链式约束
如果希望约束更紧凑,可以直接在TableType的类型绑定里关联其关联类型的约束:
pub trait GenericTable { type TableType: AsQuery + diesel::Table + QueryFragment<diesel::pg::Pg> // 直接约束AsQuery的关联类型Query where <Self::TableType as AsQuery>::Query: diesel::Table + QueryId; fn get_table(&self) -> &Self::TableType; fn count(&self, env: &mut dyn Environment) -> Result<i64, Error> { Ok(self.get_table().count().first::<i64>(env.db().connection())?) } }
关键注意点
- 修正
get_table的引用类型:原代码中count是&self方法,但get_table返回&mut Self::TableType,这会导致编译错误——不可变引用的方法无法获取可变引用,所以要把get_table改成&self参数并返回&Self::TableType。 - 适配数据库类型:
QueryFragment<diesel::pg::Pg>中的Pg要替换成你实际使用的数据库类型(比如Sqlite或Mysql)。 - 保留多态性:两种方案都不需要给
count添加泛型参数,只要结构体实现GenericTable并满足约束,就能统一调用count方法,完全保留多态性。
内容的提问来源于stack exchange,提问作者David Hankins
相关产品推荐
相关产品推荐

