async-graphql: SimpleObject: error: lifetime may not live long enough

I got some weird error after using #[graphql(complex)] and #[ComplexObject] on other struct.

So the affected struct doesn’t have the “complex” thing.

I’m probably using nested ComplexObject that uses some dataloaders.

I’ll try to make a minimal, reproducible example.

Expected Behavior

Actual Behavior

13 | #[derive(Debug, PartialEq, SimpleObject)]
   |                            -^^^^^^^^^^^
   |                            |
   |                            let's call the lifetime of this reference `'2`
   |                            let's call the lifetime of this reference `'1`
   |                            associated function was supposed to return data with lifetime `'1` but it is returning data with lifetime `'2`
   |                            in this derive macro expansion
   |
  ::: /home/bbigras/.cargo/registry/src/github.com-1ecc6299db9ec823/async-graphql-derive-3.0.38/src/lib.rs:47:1

Steps to Reproduce the Problem

Specifications

  • Version: 3.0.38
  • Platform: NixOS 22.05 (Quokka) x86_64
  • Subsystem:

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 6
  • Comments: 28 (7 by maintainers)

Commits related to this issue

Most upvoted comments

This is probably fixed now in Rust 1.62.1: https://github.com/rust-lang/rust/issues/98890

Click to see how I reached that situation

Ok, I have investigated a bit the @Bberky’s situation. They managed to reduce the issue to:

  #[derive(::async_graphql::SimpleObject)]
  pub struct A {
      field: String,
  }
  
+ pub struct B { field: String }

with a cargo c before and after that diff, on 1.62.0 , triggering a lifetime error message as reported by other people in this repo. By using an intermediary expansion trick, I got the expansion of SimpleObject on A to be:

Click to see the expansion
#[allow(clippy::all, clippy::pedantic)]
impl A {
    #[inline]
    #[allow(missing_docs)]
    async fn field(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<&String> {
        ::std::result::Result::Ok(&self.field)
    }
}
#[allow(clippy::all, clippy::pedantic)]
#[async_graphql::async_trait::async_trait]
impl async_graphql::resolver_utils::ContainerType for A {
    async fn resolve_field(
        &self,
        ctx: &async_graphql::Context<'_>,
    ) -> async_graphql::ServerResult<::std::option::Option<async_graphql::Value>> {
        if ctx.item.node.name.node == "field" {
            let f = async move {
                self.field(ctx)
                    .await
                    .map_err(|err| err.into_server_error(ctx.item.pos))
            };
            let obj = f.await.map_err(|err| ctx.set_error_path(err))?;
            let ctx_obj = ctx.with_selection_set(&ctx.item.node.selection_set);
            return async_graphql::OutputType::resolve(&obj, &ctx_obj, ctx.item)
                .await
                .map(::std::option::Option::Some);
        }
        ::std::result::Result::Ok(::std::option::Option::None)
    }
}
#[allow(clippy::all, clippy::pedantic)]
#[async_graphql::async_trait::async_trait]
impl async_graphql::OutputType for A {
    fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
        ::std::borrow::Cow::Borrowed("A")
    }
    fn create_type_info(registry: &mut async_graphql::registry::Registry) -> ::std::string::String {
        registry.create_output_type::<Self, _>(
            async_graphql::registry::MetaTypeId::Object,
            |registry| async_graphql::registry::MetaType::Object {
                name: ::std::borrow::Cow::into_owned(::std::borrow::Cow::Borrowed("A")),
                description: ::std::option::Option::None,
                fields: {
                    let mut fields = async_graphql::indexmap::IndexMap::new();
                    fields.insert(
                        ::std::borrow::ToOwned::to_owned("field"),
                        async_graphql::registry::MetaField {
                            name: ::std::borrow::ToOwned::to_owned("field"),
                            description: ::std::option::Option::None,
                            args: ::std::default::Default::default(),
                            ty: <String as async_graphql::OutputType>::create_type_info(registry),
                            deprecation: async_graphql::registry::Deprecation::NoDeprecated,
                            cache_control: async_graphql::CacheControl {
                                public: true,
                                max_age: 0usize,
                            },
                            external: false,
                            provides: ::std::option::Option::None,
                            requires: ::std::option::Option::None,
                            visible: ::std::option::Option::None,
                            compute_complexity: ::std::option::Option::None,
                        },
                    );
                    fields
                },
                cache_control: async_graphql::CacheControl {
                    public: true,
                    max_age: 0usize,
                },
                extends: false,
                keys: ::std::option::Option::None,
                visible: ::std::option::Option::None,
                is_subscription: false,
                rust_typename: ::std::any::type_name::<Self>(),
            },
        )
    }
    async fn resolve(
        &self,
        ctx: &async_graphql::ContextSelectionSet<'_>,
        _field: &async_graphql::Positioned<async_graphql::parser::types::Field>,
    ) -> async_graphql::ServerResult<async_graphql::Value> {
        async_graphql::resolver_utils::resolve_container(ctx, self).await
    }
}
impl async_graphql::ObjectType for A {}

From there, once that diff is applied in between cargo checks, I get:

error: lifetime may not live long enough
 --> /private/var/folders/m_/347zzk7j6nz08tt6z3tnd3z00000gn/T/tmp.qeHHjilha7/target/debug-proc-macros/test-f4020daedf6df8e8.rs:5:95
  |
5 |       async fn field(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<&String> {
  |  ____________________-___________________________________--_____________________________________^
  | |                    |                                   |
  | |                    |                                   let's call the lifetime of this reference `'2`
  | |                    let's call the lifetime of this reference `'1`
6 | |         ::std::result::Result::Ok(&self.field)
7 | |     }
  | |_____^ associated function was supposed to return data with lifetime `'1` but it is returning data with lifetime `'2`

error: lifetime may not live long enough
 --> /private/var/folders/m_/347zzk7j6nz08tt6z3tnd3z00000gn/T/tmp.qeHHjilha7/target/debug-proc-macros/test-f4020daedf6df8e8.rs:6:9
  |
5 |     async fn field(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<&String> {
  |                    -                                   -- let's call the lifetime of this reference `'2`
  |                    |
  |                    let's call the lifetime of this reference `'1`
6 |         ::std::result::Result::Ok(&self.field)
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated function was supposed to return data with lifetime `'2` but it is returning data with lifetime `'1`

So the interesting part is that this does not involve async_trait whatsoever (I was afraid it would), and so the following part of the expansion is the one hitting the incremental compilation bug:

#[allow(clippy::all, clippy::pedantic)]
impl A {
    #[inline]
    #[allow(missing_docs)]
    async fn field(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<&String> {
        ::std::result::Result::Ok(&self.field)
    }
}

Finally, removing SimpleObject altogether from the equation, we end up with the following diff in between cargo checks running into the incremental compilation issue:

  pub struct A {
      field: String,
  }
  
  #[allow(clippy::all, clippy::pedantic)]
  impl A {
      #[inline]
      #[allow(missing_docs)]
      async fn field(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<&String> {
          ::std::result::Result::Ok(&self.field)
      }
  }
  
+ pub struct B { field: String, }

which ends up yielding the following stand-alone repro which I’ll report to rust [EDIT: done]:

fn main(){let _=std::process::Command::new("/bin/bash").args(&["-c", concat!(r##"{
set -euo pipefail
cd $(mktemp -d)


cargo init -q --name example --lib

cat> src/lib.rs <<'EOF'
    #![allow(unused)]
    struct Foo;
    
    impl Foo {
        async fn f(&self, _: &&()) -> &() {
            &()
        }
    }
EOF

(set -x
    cargo c -q
    { set +x; echo 'enum Bar {}'; } 2>/dev/null | tee -a src/lib.rs
    cargo c -q
)


echo ✅
} 2>&1"##)]).status();}

In the meantime, @sunli829 (or other maintainers of this crate), you may be able to dodge the compiler limitation by using https://docs.rs/fix-hidden-lifetime-bug on the non-async_trait-annotated async fns 🙂

Just started getting this, can’t reliably trigger or reliably stop it from happening 😂 cargo clean temporarily fixes it as others said, perhaps something related to compilation order, or something not being recompiled?

async-graphql and async-graphql-axum 4.0.4 - stable-aarch64-apple-darwin

I have a simple script now for this until there is some workaround as doing cargo clean just takes way too long for me. The issue happens quite often for me, when the compiler complains about other things beforehand for some reason this issue pops up even though the other issues have been fixed. If your workflow consists of cargo watch -x "run" and having an autosave in your editor just makes this issue very common.

cargo clean -p async-graphql -p *your crate name*

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

I tested my https://github.com/async-graphql/async-graphql/issues/900#issuecomment-1175787840 to confirm nightly-2022-07-23 (rustc 1.64.0-nightly (848090dcd 2022-07-22)) is fixed.

@ControlCplusControlV you should be able to save in compile times by using that PR through a patch rather than fully disabling incremental altogether 🙂:

[patch.crates-io.async-graphql]
git = "https://github.com/danielhenrymantilla/async-graphql.git"
branch = "workaround-for-1-62-bug"

I was able to fix this issue by simple running export CARGO_INCREMENTAL=0, requires a better workstation to remain productive but it does work