Diesel Async Pagination Triggers Lifetime Error in async-graphql #[Object] Resolver

Hey all! I’m encountering a confusing lifetime issue in a project using diesel_async, a custom pagination trait, and async-graphql.

The error doesn’t occur when I write the query inline, but does occur when I move the query into a build_query() function, even though I try to give it a 'static lifetime.

My custom pagination trait comes from this article: https://rymc.io/blog/2024/pagination-in-diesel-async/


:white_check_mark: What works (inline)

let (items, total) = users::table
    .left_join(...)
    .select(...)
    .paginate(page)
    .per_page(per_page)
    .load_and_count_pages::<MyModel>(&mut conn)
    .await?;

This works and compiles fine.


:cross_mark: What fails (via function)

fn build_query(...) -> IntoBoxed<'static, _, Pg> {
    users::table
        .left_join(...)
        .into_boxed()
}
let query = build_query(...);

let (items, total) = query
    .paginate(page)
    .per_page(per_page)
    .load_and_count_pages::<MyModel>(&mut conn)
    .await?;

This causes a lifetime error, but the error doesn’t appear at the call site — instead, it surfaces at the #[Object] macro from async-graphql:

error[E0477]: the type `Paginated<BoxedSelectStatement<'_, ...>>` does not fulfill the required lifetime
  --> src/http/handlers/my_handler.rs:23:1
   |
23 | #[Object]
   | ^^^^^^^^^

:magnifying_glass_tilted_left: What I’ve tried

  • Made all input arguments owned (no references)
  • Explicitly set the return type of build_query() to BoxedSelectStatement<'static, _, _, Pg, _>

:folded_hands: What I’d love help with

  1. Why does returning a boxed query from a function break .paginate().await in a #[Object] resolver?
  2. How can I structure the query builder so I can paginate it asynchronously inside #[Object]?
  3. Are there any best practices to avoid these lifetime traps when using Diesel + async + GraphQL?

Context

Rust version:

rustc 1.85.1 (4eb161250 2025-03-15)

Dependencies:

axum = { version = "0.8.1", features = ["multipart"] }
deadpool-diesel = { version = "0.6.1", features = ["postgres", "tracing"] }
diesel = { version = "2.2.8", features = ["chrono", "postgres", "serde_json"] }
diesel-async = { version = "0.5.2", features = ["deadpool", "postgres"] }
...

Any suggestions — even hacky workarounds — would be greatly appreciated! :folded_hands:
Sorry if this is a noob question — I’m still new to Rust and still wrapping my head around lifetimes :sweat_smile:
I truly appreciate any pointers or explanations, even if they seem basic!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.