Hidden type for captures lifetime that does not appear in bounds

This is almost minimal reproducible example for my case, I left some details for clarity

#![feature(type_alias_impl_trait)]

use std::marker::PhantomData;

mod crate1 {
    use super::*;

    pub struct Request<'a, R>(PhantomData<&'a R>);
    pub struct RequestReader<'a, R>(PhantomData<&'a R>);
    pub trait Handler {
        async fn call<R>(&self, request: Request<'_, R>);
    }

    impl<'a, R> Request<'a, R> {
        pub fn reader<'b>(&'b self) -> RequestReader<'b, R> {
            RequestReader(PhantomData)
        }
    }
}

mod crate2 {
    use super::*;

    use crate1::{Handler, Request};

    trait Provider {
        async fn provide<R, P: Provider>(&self, f: SaverFn<R, P>);
    }

    struct Saver<P: Provider> {
        provider: P,
    }
    type SaverFn<R, P: Provider> = impl AsyncFnOnce(&mut ());

    impl<P: Provider> Handler for Saver<P>
    where
        P: Provider,
    {
        #[define_opaque(SaverFn)]
        async fn call<R>(&self, request: Request<'_, R>) {
            let reader = request.reader(); // Without this reborrow I can make it compile
            // Hopefully `super let` will be stabilized and it would
            // become `let writer = (self.provide)().await;`
            self.provider.provide::<R, P>(async |writer| {
                core::hint::black_box((reader, writer));
            });
        }
    }
}

First of all, if replace TAIT AsyncFnOnce with a struct that has async method, error goes away immediately.

Second, if add a lifetime to SaverFn, you need to add lifetime to provide. But it will trigger that error:

 1  error[E0581]: return type references lifetime `'a`, which is not constrained by the fn input types
   --> src/main.rs:27:9
    |
 27 |         async fn provide<'a, R, P: Provider>(&self, f: SaverFn<'a, R, P>);
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

My question is, should I open an issue or two? Or maybe it is expected and you can tell me what I did wrong?

I think the second error is because the TAIT isn't defined for all lifetimes, similarly to how you have frequently have to put where Self: 'gat_lifetime on GATs so they're not required to be defined for 'static, say.

I poked a little but didn't get far. Can you show how this works:

            // Without this reborrow I can make it compile
            let reader = request.reader();

And clarify what you meant by:

            // Hopefully `super let` will be stabilized and it would
            // become `let writer = (self.provide)().await;`
1 Like

Here's a possible solution.

  • I added some explicit lifetimes with bounds to Provider::provide
  • I updated SaverFn to include captured generic lifetimes
  • I created a helper function to define SaverFn which captures &'bor Request<..>

Some bread crumbs on how I got there follow.

Does this warrant an issue... maybe? It's unhelpful diagnostics if nothing else.


I poked around some more and got to here. The extra function to define the opaque gets rid of the temporary lifetime capture. But the unconstrained error comes from here:

    trait Provider {
        fn provide<'bor, 'rd, R, P: Provider>(&self, f: SaverFn<'bor,'rd, R, P>)
            -> impl Future<Output = ()> + use<'_, R, P, Self>;
    }

When it's an async fn, or when it captures 'bor or 'rd, you get the unconstrained bound.

Ah, I have seen something like this before. For some reason it wants early-bound lifetimes here, not late-bound ones.

    // This one works
    trait Provider {
        fn provide<'bor, 'rd: 'bor, R: 'rd, P: Provider>(&self, f: SaverFn<'bor,'rd, R, P>)
            -> impl Future<Output = ()> + use<'_, 'bor, 'rd, R, P, Self>;
    }

Having written that out, it's sketchy that the TAIT doesn't require 'rd: 'bor but does require 'R: 'rd. In fact, you can also replace 'rd: 'bor with 'bor: 'bor and it still compiles, you just need 'bor to be early bound. :person_shrugging:

1 Like

This is probably because 'rd is not actually being captured in the async closure due to this signature...

pub fn reader<'bor>(self: &'bor Reader<'rd>) -> RequestReader<'bor, R>

...and I think the bounds on the TAIT are implied elsewhere, so on Provider::provide you just need to make the lifetimes early bound, and it was never realy about R's outlives there.

So here's a version with one less lifetime on the TAIT.


Edit: I created a diagnostic issue.

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.