我这里有一个 MRE 仓库:https://github.com/hansl/mre-issue-higher-ranked
本质上,我使用的是 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}\n")
}
我定义
paginate
和 load_and_count_total
函数如下(在 pages.rs
模块中):
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,
{
// Ignore those linting errors. `get(0)` cannot be replaced with `first()`.
#![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::Query` is not general enough
--> src/main.rs:12:1
|
12 | #[get("/")]
| ^^^^^^^^^^^ implementation of `diesel::query_builder::Query` is not general enough
|
= note: `diesel::query_builder::Query` would have to be implemented for the type `BoxedSelectStatement<'0, (Integer, Text), FromClause<table>, Pg>`, for any lifetime `'0`...
= note: ...but `diesel::query_builder::Query` is actually implemented for the type `BoxedSelectStatement<'1, (Integer, Text), FromClause<table>, Pg>`, for some specific lifetime `'1`
= note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info)
我已经在
load_and_count_total
中尝试了很多不同的生命周期组合并指定或限制类型,但我就是无法让它发挥作用。我能做的最好的事情就是删除修复错误的into_boxed()
,但我需要它,因为在我的现实世界原始代码中条件变得指数级复杂,并且我需要能够有条件地过滤我的查询。
实际的代码要复杂得多,但我试图将其降低到我能得到的最简单的再现形式。除了
main.rs
和 pages.rs
之外的所有模块都可以在存储库中忽略。
我希望
load_and_count_total
能够按预期工作,而不会出现有关不同生命周期的编译器错误。
做一些研究让我相信这是一个编译器错误,已记录在仍然未解决的问题中,但似乎也可能有一个不涉及修复编译器的解决方案。我只是找不到我的特定代码。
好吧,经过几天(以及使用 MRE 后的几个小时)调查这个特定问题,我找到了一种方法来告诉编译器我的代码是安全的。
我没有将
load_and_count_total
声明为 async
,而是将声明更改为以下内容:
impl<T: Query> Paginated<T> {
pub fn load_and_count_total<'a, U>(
self,
conn: &'a mut AsyncPgConnection,
) -> impl std::future::Future<Output = QueryResult<(Vec<U>, i64)>> + Send + 'a
where
Self: LoadQuery<'a, AsyncPgConnection, (U, i64)>,
U: Send + 'a,
T: 'a,
{
// Ignore those linting errors. `get(0)` cannot be replaced with `first()`.
#![allow(clippy::get_first)]
let results = self.load::<(U, i64)>(conn);
async move {
let results = results.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))
}
}
}
请注意返回类型的额外
+ Send + 'a
限制,这让编译器知道 Future 在 'a
和 Send
的整个持续时间内都处于活动状态。似乎两者都是编译器停止抱怨所必需的,但如果声明了该函数,编译器实际上不会自动推断出这两者async
。
这解决了上述问题。