Is there a solution to "lifetime bounds not satisfied" (higher-ranked lifetime error in GAT)?

I'm working on a json schema crate and part of the functionality being built is auto-resolution of referenced schemas. In order to facilitate this, my compile flow needs to be async. However, I'm running into the lifetime error below and I do not know how to solve it and need some help, please.

The error points to Unexpected higher-ranked lifetime error in GAT usage · Issue #100013 · rust-lang/rust · GitHub but I don't see a simple workaround, or at least one I recognize.

I apologize about the amount of code below. I was unable to create a repro that was relevant to my case and also not unwieldy. I've omitted as much code as I thought possible but all of source is currently available at GitHub - chanced/grill.

// https://github.com/chanced/grill/blob/b9dbcb7185c5f5a5f37c03f9eba22e7338891d2b/grill-core/src/resolve.rs#L22-L32
pub trait Resolve {}

// https://github.com/chanced/grill/blob/b9dbcb7185c5f5a5f37c03f9eba22e7338891d2b/grill-core/src/lang.rs#L105-L122
pub struct Compile<'int, 'txn, 'res, L, R, K>
where
    L: Language<K>,
    K: 'static + Key + Send + Sync,
{
    pub uris: Vec<AbsoluteUri>,
    pub txn: Transaction<'int, 'txn, L, K>,
    pub resolve: &'res R,
    pub validate: bool,
}

// https://github.com/chanced/grill/blob/b9dbcb7185c5f5a5f37c03f9eba22e7338891d2b/grill-core/src/state.rs#L54-L72
pub struct Transaction<'int, 'txn, L: Language<K>, K: 'static + Key + Send + Sync> {
    pub schemas: &'txn mut Schemas<L::CompiledSchema, K>,
    pub sources: &'txn mut Sources,
    pub cache: &'int mut Cache,
}

// https://github.com/chanced/grill/blob/b9dbcb7185c5f5a5f37c03f9eba22e7338891d2b/grill-core/src/lang.rs#L41-L82
#[trait_variant::make(Send)]
pub trait Language<K>: Sized + Clone + fmt::Debug
where
    K: 'static + Key + Send + Sync,
{
    type CompiledSchema: schema::CompiledSchema<K>;
    type CompileError<R>: 'static + Send + std::error::Error
    where
        R: 'static + Resolve;
    type EvaluateResult<'val>
    where
        Self: 'val;

    async fn compile<'int, 'txn, 'res, R>(
        &'int mut self,
        compile: Compile<'int, 'txn, 'res, Self, R, K>,
    ) -> Result<Vec<K>, Self::CompileError<R>>
    where
        R: 'static + Resolve + Send + Sync;
}

// https://github.com/chanced/grill/blob/main/grill-json-schema/src/lib.rs#L53C3-L144
pub struct JsonSchema<K, S>
where
    K: 'static + Key + Send + Sync,
    S: Specification<K>,
{
    spec: S,
    _marker: PhantomData<K>,
}

impl<K, S> Language<K> for JsonSchema<K, S>
where
    K: 'static + Key + Send + Sync,
    S: Specification<K> + Send + 'static,
{
    type CompiledSchema = CompiledSchema<S, K>;
    type CompileError<R> = S::CompileError<R>
    where
        R: 'static + Resolve;
    type EvaluateResult<'val> =
        Result<Report<S::Annotation<'val>, S::Error<'val>>, S::EvaluateError>;

    async fn compile<'int, 'txn, 'res, R>(
        &'int mut self,
        ctx: lang::Compile<'int, 'txn, 'res, Self, R, K>,
    ) -> Result<Vec<K>, Self::CompileError<R>>
    where
        R: 'static + Resolve + Send + Sync,
    {
        compile::compile::<R, S, K>(self.spec.init_compile(ctx).await?).await
    }
}
error: lifetime bound not satisfied
   --> grill-json-schema/src/lib.rs:126:5
    |
126 | /     async fn compile<'int, 'txn, 'res, R>(
127 | |         &'int mut self,
128 | |         ctx: lang::Compile<'int, 'txn, 'res, Self, R, K>,
129 | |     ) -> Result<Vec<K>, Self::CompileError<R>>
130 | |     where
131 | |         Self: 'int + 'txn + 'res,
132 | |         R: 'static + Resolve + Send + Sync,
    | |___________________________________________^
    |
    = note: this is a known limitation that will be removed in the future (see issue #100013 <https://github.com/rust-lang/rust/issues/100013> for more information)

Is there anyway to work around this? I'm sorry that this is such a big ask. I don't know how to fix it and don't know where to even start on getting a solution in the works.

I appreciate you taking the time to even read this. Thank you.

I'm starting to think I should just remove schema fetching/resolution and dump async from the equation.

edit actually, it really needs to be async. With JSON Schema, URIs can be assigned as part of the schema. I can't isolate the resolution portion without basically shadow compiling to get the needed URIs to resolve.

When I had this problem there were only two solutions I have found to work:

  1. Remove Send.
  2. Remove default implementations and put their code into macros which generates implementations for concrete types. It was essentially “remove generic parameter Self”, and from what I see this variant does not apply to your code. (It worked for my functions which returned something like impl Future<Output = Result<Vec<Self>> + Send + 'conn which means that you would need to deal with K and, probably, also R.)

Note that for the first part you need to turn async fn foo(…) -> Ret into fn foo(…) -> impl Future<Output = Ret> + Send. I do not know any way to work around this problem while keeping futures Send.

1 Like

Oh, that's interesting. If I'm understanding the problem correctly, I should be able to have an associated type: Future<Output = Result<_,_>, which I associate a hand-rolled future in the impl, I can remove #[trait_variant::make(Send)] and thus eliminate the need for it to be Send, getting me passed this?

I'm going to give that a shot. Thank you very much!

It is not handrolling the future, it is easy mechanical change from

async fn foo() -> () {
    code
}

to

fn foo() -> impl Future<Output = ()> + Send {
    async move {
        code
    }
}

. Code is basically the same, but this does not rely on compiler (not) blessing the real output type (which is impl Future for async fn) with extra Send bounds automatically.

You do not need any extra associated types as without TAIT it would be way too much more code to implement.

3 Likes

Oooooh, I didn't know you could -> impl SomeTrait in traits!

Thanks! Definitely going to give this a shot!

(Note: there is some minor misunderstanding: I thought that it is compiler which adds Send as I incorrectly remembered reasoning behind async_fn_in_trait warning, but it is actually trait_variant which I missed.)

1 Like

It compiles! Thank you so much! I was so discouraged, thinking I'd have to go back to the drawing board.

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.