`diesel::query_builder::Query` 的实现不够普遍错误

问题描述 投票:0回答:1

我这里有一个 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
能够按预期工作,而不会出现有关不同生命周期的编译器错误。

做一些研究让我相信这是一个编译器错误,已记录在仍然未解决的问题中,但似乎也可能有一个不涉及修复编译器的解决方案。我只是找不到我的特定代码。

rust lifetime rust-diesel rust-rocket
1个回答
0
投票

好吧,经过几天(以及使用 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

这解决了上述问题。

© www.soinside.com 2019 - 2024. All rights reserved.