/fix_hidden_lifetime_bug.rs

Proc-macro to write an automatic fix for the "hidden lifetime in impl Trait" issue

Primary LanguageRustApache License 2.0Apache-2.0

::fix_hidden_lifetime_bug

Repository Latest version Documentation MSRV unsafe forbidden License CI

Are you getting one of the following errors (E700)?

  • error[E0700]: hidden type for `impl Trait` captures lifetime that does not appear in bounds
      --> examples/main.rs:13:40
       |
    13 | fn foo<'a, 'b>(it: &'a mut &'b ()) -> impl 'a + Sized {
       |                                       ^^^^^^^^^^^^^^^
       |
    note: hidden type `&'a mut &'b ()` captures the lifetime `'b` as defined on the function body at 13:12
      --> examples/main.rs:13:12
       |
    13 | fn foo<'a, 'b>(it: &'a mut &'b ()) -> impl 'a + Sized {
       |            ^^
    Problematic code
    fn foo<'a, 'b>(it: &'a mut &'b ()) -> impl 'a + Sized {
        it
    }

  • error[E0700]: hidden type for `impl Trait` captures lifetime that does not appear in bounds
     --> examples/main.rs:8:45
      |
    8 | async fn bar<'a> (_: &(), _: Box<dyn Send>) {
      |                                             ^
      |
    note: hidden type `impl Future` captures lifetime smaller than the function body
     --> examples/main.rs:8:45
      |
    8 | async fn bar<'a> (_: &(), _: Box<dyn Send>) {
      |                                             ^
    Problematic code
    async fn bar<'a> (_: &(), _: Box<dyn Send>) {
        /* … */
    }

    EDIT: Fixed as of 1.69.0


  • error[E0700]: hidden type for `impl Trait` captures lifetime that does not appear in bounds
     --> examples/main.rs:4:57
      |
    4 | async fn baz<'a>(a: &'static (), b: &(), c: &()) {
      |                                                  ^
      |
    note: hidden type `impl Future` captures lifetime smaller than the function body
     --> examples/main.rs:4:57
      |
    4 | async fn baz<'a>(a: &'static (), b: &(), c: &()) {
      |                                                  ^
    Problematic code
    async fn baz<'a>(a: &'static (), b: &(), c: &()) {
        /* … */
    }

    EDIT: Fixed as of 1.69.0


Then you can add the attribute provided by this crate to automagically generate an equivalent signature that soothes such a grumpy compiler 🙃

  • See the lifetime bug async issue, as well as this other comment for more info.

    The fix is thus to perform the unsugaring from an async fn to an fn yielding a Future, and then just adding the necessary + Captures<'_> bounds.

  • See also this post where I explain the issue more in detail.

Usage

  1. cargo add fix_hidden_lifetime_bug, or add the following to your Cargo.toml file:

    [dependencies]
    fix-hidden-lifetime-bug = "x.y.z"
    • where you can find the version using cargo search fix_hidden_lifetime_bug
  2. Add the following to your lib.rs file:

    #[macro_use]
    extern crate fix_hidden_lifetime_bug;
  3. Slap a #[fix_hidden_lifetime_bug] on the problematic function:

    #[fix_hidden_lifetime_bug] // <-- Add this!
    fn foo<'a, 'b>(it: &'a mut &'b ()) -> impl 'a + Sized {
        it
    }
    #[fix_hidden_lifetime_bug] // <-- Add this!
    async fn baz<'a>(fst: &'static str, snd: &str, thrd: &str) {
        /* … */
    }

Extra features

  • Full support for methods

    In the case of methods, the Self type may be hiding lifetime parameters on its own, in which case a macro annotation on the method alone may not have enough syntactical information to generate the fix:

    use ::fix_hidden_lifetime_bug::fix_hidden_lifetime_bug;
    
    struct Invariant<'lt> (
        fn(&()) -> &mut &'lt (),
    );
    
    impl Invariant<'_> {
        #[fix_hidden_lifetime_bug]
        fn quux(&self) -> impl '_ + Sized { self }
    }

    In that case, the fix is to also decorate the whole impl block with the attribute:

    use ::fix_hidden_lifetime_bug::fix_hidden_lifetime_bug;
    
    struct Invariant<'lt> (
        fn(&()) -> &mut &'lt (),
    );
    
    #[fix_hidden_lifetime_bug]
    impl Invariant<'_> {
        #[fix_hidden_lifetime_bug]
        fn quux(&self) -> impl '_ + Sized { self }
    }

  • Displaying the expansions

    By enabling the "showme" Cargo feature:

    [dependencies]
    fix-hidden-lifetime-bug.version = "x.y.z"
    fix-hidden-lifetime-bug.features = ["showme"]

    you can then feed a showme parameter to specific #[fix_hidden_lifetime_bug] annotations, as follows:

    #[fix_hidden_lifetime_bug(showme)]
    Example
    use ::fix_hidden_lifetime_bug::fix_hidden_lifetime_bug;
    
    #[fix_hidden_lifetime_bug(showme)]
    async fn baz<'a>(a: &'static (), b: &'_ (), c: &'_ ()) {
        println!("Hello, World!");
    }

    outputs:

    fn baz<'a, '_0, '_1, '__async_fut>(
        a: &'static (),
        b: &'_0 (),
        c: &'_1 (),
    ) -> impl '__async_fut
          + ::fix_hidden_lifetime_bug::core::future::Future<Output = ()>
          + ::fix_hidden_lifetime_bug::Captures<'a>
          + ::fix_hidden_lifetime_bug::Captures<'_0>
          + ::fix_hidden_lifetime_bug::Captures<'_1>
    where
        &'static (): '__async_fut,
        &'_0 (): '__async_fut,
        &'_1 (): '__async_fut,
    {
        async move {
            "Mention the input vars so that they get captured by the Future";
            let (a, b, c) = (a, b, c);
            println!("Hello, World!");
        }
    }

    This will make the attribute display the result of its expansion (and its expansion only! Hence yielding output that is way more readable than that from cargo expand or other such tools), basically showing you how to manually fix a given function signature if you so wish (e.g., to avoid depending on proc-macro processing every time the annotated function is compiled, or to make the life easier for IDEs).

    Should you fix the signature, you may then be interested in:

  • Opting out of the magic proc-macro attribute

    If you don't want to have to recompile each time the proc-macro able to fix function signatures for you (e.g., you rather want it to show you how to fix the signature so that you can do it through exclusive usage of + Captures<'…> additions), so as not to have to pay the proc-macro compilation time each time you compile from scratch, then you can opt out of it by disabling the default-features of the crate: this will disable the proc-macros features, which is the one that brings it to the table.

    That way, you can still use this then very lightweight crate just for its Captures<'…> (and maybe MentionsTy<…>) definitions, and the documentation that goes with it!

    [dependencies]
    
    fix-hidden-lifetime-bug.version = "x.y.z"
    fix-hidden-lifetime-bug.default-features = false