diesel::query_builder::Query实现不够通用编译错误求助
问题背景
在使用Rocket框架结合Diesel ORM构建分页查询时,当用into_boxed()构建动态条件查询后,调用自定义的paginate()和load_and_count_total()方法会触发编译错误,提示diesel::query_builder::Query实现不够通用,具体错误指向BoxedSelectStatement的生命周期约束不满足宏的通用要求。
相关代码
Rocket处理器函数
#[get("/")] async fn index(mut db: Db) -> String { let query = schema::posts::table.into_boxed(); let (items, total): (Vec<models::Post>, i64) = query .paginate(Some(0)) .load_and_count_total(&mut db) .await .unwrap(); format!("{items:?} {total}") }
自定义分页Trait及实现
pub trait Paginate: Sized { fn paginate(self, page: Option<i64>) -> Paginated<Self>; } impl<T: Query> Paginate for T { fn paginate(self, page: Option<i64>) -> Paginated<Self> { let page = page.unwrap_or(0); Paginated { query: self, per_page: DEFAULT_PER_PAGE, page, offset: page * DEFAULT_PER_PAGE, } } } impl<T> Paginated<T> { pub async fn load_and_count_total<'a, U>( self, conn: &mut AsyncPgConnection, ) -> QueryResult<(Vec<U>, i64)> where Self: LoadQuery<'a, AsyncPgConnection, (U, i64)>, U: Send, T: 'a, { #![allow(clippy::get_first)] let results = self.load::<(U, i64)>(conn).await?; let total = results.get(0).map(|x| x.1).unwrap_or(0); let records = results.into_iter().map(|x| x.0).collect(); Ok((records, total)) } }
编译错误信息
error: implementation of
diesel::query_builder::Queryis not general enough
--> src/main.rs:12:1
|
#[get("/")] ^^^^^^^^^^^ implementation of diesel::query_builder::Queryis not general enough= note:
diesel::query_builder::Querywould have to be implemented for the typeBoxedSelectStatement<'0, (Integer, Text), FromClause<table>, Pg>, for any lifetime'0...
= note: ...butdiesel::query_builder::Queryis actually implemented for the typeBoxedSelectStatement<'1, (Integer, Text), FromClause<table>, Pg>, for some specific lifetime'1
= note: this error originates in the attribute macroget(in Nightly builds, run with -Z macro-backtrace for more info)
解决方案
问题核心是load_and_count_total的生命周期约束过于严格,仅要求Self对特定生命周期'a实现LoadQuery,但Rocket路由宏需要类型满足任意生命周期的通用性要求。通过**更高阶生命周期(HRTB)**调整约束即可解决。
修改Paginated的load_and_count_total方法:
impl<T> Paginated<T> { pub async fn load_and_count_total<U>( self, conn: &mut AsyncPgConnection, ) -> QueryResult<(Vec<U>, i64)> where // 使用HRTB要求Self对任意连接生命周期'conn都实现LoadQuery for<'conn> Self: LoadQuery<'conn, AsyncPgConnection, (U, i64)>, U: Send, { #![allow(clippy::get_first)] let results = self.load::<(U, i64)>(conn).await?; let total = results.get(0).map(|x| x.1).unwrap_or(0); let records = results.into_iter().map(|x| x.0).collect(); Ok((records, total)) } }
关键修改点
- 移除方法上的
'a生命周期参数和T: 'a约束 - 将
Self: LoadQuery<'a, ...>替换为for<'conn> Self: LoadQuery<'conn, ...>,明确要求Self对任意连接生命周期都能实现LoadQuery,满足Rocket宏对类型通用性的要求
调整后,编译器可正确推导BoxedSelectStatement的生命周期,既能保留into_boxed()的动态查询能力,又能正常使用分页方法。
内容的提问来源于stack exchange,提问作者Hans

