Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Higher rank lifetimes fails when trying to set closure lifetimes #116869

Closed
GregoryConrad opened this issue Oct 18, 2023 · 2 comments
Closed

Higher rank lifetimes fails when trying to set closure lifetimes #116869

GregoryConrad opened this issue Oct 18, 2023 · 2 comments
Labels
A-borrow-checker Area: The borrow checker A-closures Area: closures (`|args| { .. }`) A-lifetimes Area: lifetime related C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@GregoryConrad
Copy link

GregoryConrad commented Oct 18, 2023

I'm having a hard time minimizing this code sample, so sorry in advance.

The real issue I'm facing is #111662, so please see my comment for some context.
However, since that issue isn't fixed, I needed a workaround.
Here's the basic jist of my workaround:

fn main() {
    // ERROR: 
    let _c = |b: S<'_>| -> &() { b.thing() }; //~ ERROR lifetime may not live long enough
    // WORKAROUND:
    let _c = fix_lifetime(|b: S| b.thing());
}

// Let's help the compiler out a little:
fn fix_lifetime<F, T>(f: F) -> F
where
    F: for<'a> FnOnce(S<'a>) -> &'a T,
{
    f
}

struct S<'a>(&'a ());
impl<'a> S<'a> {
    fn thing(self) -> &'a () {
        self.0
    }
}

This workaround works for the provided example and I'm guessing 99% of other situations.

But, my actual issue is a bit more complicated so I get a:

error: higher-ranked lifetime error
  --> rearch/src/side_effects.rs:13:5
   |
13 |     fix_lifetime(move |register: SideEffectRegistrar| register.raw(initial))
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

No further information is provided.

Full copy of the problematic code
// This trait is the main problem. It is equivalent to FnOnce(SideEffectRegistrar) -> T
pub trait SideEffect {
    type Api<'a>;
    fn build<'a>(self, registrar: SideEffectRegistrar<'a>) -> Self::Api<'a>;
}
impl<T, F: FnOnce(SideEffectRegistrar) -> T> SideEffect for F {
    type Api<'a> = T;
    fn build<'a>(self, registrar: SideEffectRegistrar<'a>) -> Self::Api<'a> {
        self(registrar)
    }
}

pub fn raw<T: Send + 'static>(
    initial: T,
) -> impl for<'a> SideEffect<
    Api<'a> = (
        &'a mut T,
        impl Fn(Box<dyn FnOnce(&mut T)>) + Clone + Send + Sync,
    ),
> {
    fix_lifetime(move |register: SideEffectRegistrar| register.raw(initial)) // ------------------- ERROR HERE
}

// Workaround from above to try to fix the underlying issue.
fn fix_lifetime<F, T, R>(f: F) -> F
where
    F: for<'a> FnOnce(SideEffectRegistrar<'a>) -> (&'a mut T, R),
{
    f
}


// Not sure if this is needed, but here it is just in case:
pub struct SideEffectRegistrar<'a> {
    side_effect: &'a mut OnceCell<Box<dyn Any + Send>>,
    rebuilder: Box<dyn SideEffectRebuilder>,
}
impl<'a> SideEffectRegistrar<'a> {
    // Other methods left out for brevity.
    pub(crate) fn raw<T>(
        self,
        initial: T,
    ) -> (
        &'a mut T,
        impl Fn(Box<dyn FnOnce(&mut T)>) + Clone + Send + Sync + 'static,
    )
    where
        T: Send + 'static,
    {
        self.side_effect.get_or_init(|| Box::new(initial));
        let data = self
            .side_effect
            .get_mut()
            .expect(PREVIOUS_INIT_FAILED_MSG)
            .downcast_mut::<T>()
            .unwrap_or_else(|| panic!("{}", EFFECT_FAILED_CAST_MSG));
        let rebuild = move |mutation: Box<dyn FnOnce(&mut T)>| {
            (self.rebuilder)(Box::new(|data| {
                let data = data
                    .downcast_mut::<T>()
                    .unwrap_or_else(|| panic!("{}", EFFECT_FAILED_CAST_MSG));
                mutation(data);
            }));
        };
        (data, rebuild)
    }
}

Meta

Tested on nightly.

@GregoryConrad GregoryConrad added the C-bug Category: This is a bug. label Oct 18, 2023
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Oct 18, 2023
@Kartikey-Bartwal

This comment was marked as off-topic.

@fmease fmease added A-lifetimes Area: lifetime related A-closures Area: closures (`|args| { .. }`) T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Nov 26, 2023
@fmease fmease added A-borrow-checker Area: The borrow checker and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Mar 1, 2024
@fmease
Copy link
Member

fmease commented Mar 1, 2024

Right, this is a known limitation (#58052) of lifetime inference for closure arguments. Basically, rustc never infers those lifetimes as higher-ranked. Therefore new syntax has been introduced behind an experimental feature flag called closure_lifetime_binder (#97362) to explicitly introduce higher-ranked lifetimes in these positions.

Applying the following diff makes your code compile:

- let _c = |b: S<'_>| -> &() { b.thing() };
+ let _c = for<'a> |b: S<'a>| -> &'a () { b.thing() };

Closing as duplicate of #58052.

@fmease fmease closed this as not planned Won't fix, can't repro, duplicate, stale Mar 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-borrow-checker Area: The borrow checker A-closures Area: closures (`|args| { .. }`) A-lifetimes Area: lifetime related C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

4 participants