rustlifetimerust-dieselrust-rocket

Implementation of `diesel::query_builder::Query` is not general enough error


I have a MRE repo here: https://github.com/hansl/mre-issue-higher-ranked

Essentially, I'm using a Rocket handler function like so:

#[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")
}

I'm defining the paginate and load_and_count_total functions as follow (in the pages.rs module):

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))
    }
}

The error given while compiling is:

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)

I've tried a lot of various combinations of lifetimes in load_and_count_total and specifying or restricting types, I just cannot get this to work. Best I could do is remove the into_boxed() which fixes the error, but I need it as the conditions get exponentially complex in my real world original code and I need to be able to conditionally filter my queries.

The actual code is much more complex, but I've tried to lower it to the simplest form of reproduction I could get. All modules except main.rs and pages.rs can be ignored in the repo.

Expected Results

I would expect load_and_count_total to work as expected without a compiler error about different lifetimes.

Doing some research leads me to believe this is a compiler error that's been documented in issues that are still opened, but it seems there also might be a solution that doesn't involve fixing the compiler. I just cannot find it for my specific code.


Solution

  • Okay after a few days (and hours after playing with the MRE) of investigating this specific issue, I figured a way to tell the compiler that my code is safe.

    Instead of declaring load_and_count_total as async, I changed the declaration to the following:

    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))
            }
        }
    }
    

    Notice the extra + Send + 'a limitations on the return type, which let the compiler know that the Future is alive for the whole duration of 'a, and the Send. Seems that both are necessary for the compiler to stop complaining, but neither are actually inferred by the compiler automatically if the function is declared async.

    This fixes the issue above.