Poor error message related to higher rank lifetimes

f8e307b
Opened by iopq at 2024-09-24 23:03:15

https://bitbucket.org/iopq/fizzbuzz-in-rust/src/6d739f4781c90be95ac47e067562471b0c52f9f8/src/lib.rs?at=error&fileviewer=file-view-default

After I try to use the tool.rs implementation of second I get this error message:

   Compiling fizzbuzz v0.0.1 (file:///C:/Users/Igor/Documents/rust/fizzbuzz)
error[E0281]: type mismatch: the type `fn(_) -> _ {tool::second::<_>}` implements the trait `std::ops::FnMut<(_,)>`, but the trait `for<'r> std::ops::FnMut<(&'r _,)>` is required (expected concrete lifetime, found bound lifetime parameter )

  --> src\lib.rs:52:11
   |
52 |            .filter(apply(second, i))
   |                    ^^^^^
   |
   = note: required by `apply`

error[E0271]: type mismatch resolving `for<'r> <fn(_) -> _ {tool::second::<_>} as std::ops::FnOnce<(&'r _,)>>::Output == _`
  --> src\lib.rs:52:11
   |
52 |            .filter(apply(second, i))
   |                    ^^^^^ expected bound lifetime parameter , found concrete lifetime
   |
   = note: concrete lifetime that was found is lifetime '_#11r
   = note: required by `apply`

I still don't quite understand this error message. The lifetimes are anonymous, so I don't know what it's talking about. The 'r lifetime is elided, I assume? There's no error code to help me understand the issue with concrete lifetimes vs. bound lifetime parameters.

  1. Its saying that your fn(_) -> _ {tool::second::<_>} must implement std::ops::FnMut<(&'r _,)> for all possible lifetimes 'r (∀, for all, any, arbitrary), unrelated to any other lifetime. The function only implements std::ops::FnMut(_,)>, i.e. a FnMut which does not really satisfy that requirement.

    To help with reasoning why this error is reported consider an equivalent:

    filter(apply(|c| second(c), i))
    

    The types here are:

    • c: &&(&str, &std::ops::Fn(i32) -> bool);
    • second: fn second<P: Second>(seq: P) -> P::Second;
    • &(&str, &std::ops::Fn(i32) -> bool): Second, but not &&(&str, &std::ops::Fn(i32) -> bool): Second;

    At this point compiler tries these things:

    • Look for &&(&str, &std::ops::Fn(i32) -> bool): Second, which is not satisfied;
    • Noting that &(&str, &std::ops::Fn(i32) -> bool): Second does hold try finding the second: for<'r> Fn*(&'r T) where T = &(&str, &std::ops::Fn(i32) -> bool).

    I feel like the error reported in such circumstances cannot be pointing at the right place 100% of the time, as the actual error might be either that &&(_,_): Second is not satisfied or the one that you just got, and user might be interested in either of those.


    The solution fixing the error would be to dereference the c somehow… for example this way:

    filter(apply(|&c| second(c), i))
    

    Simonas Kazlauskas at 2016-10-22 23:40:41

  2. filter(apply(|c| second(c), i)) gives a really NICE error message:

    error[E0277]: the trait bound `&&(&str, &std::ops::Fn(i32) -> bool): tool::sequence::Cons` is not satisfied
      --> src\lib.rs:52:27
       |
    52 |         .filter(apply(|c| second(c), i))
       |                           ^^^^^^ trait `&&(&str, &std::ops::Fn(i32) -> bool): tool::sequence::Cons` not satisfied
       |
       = help: the following implementations were found:
       = help:   <(A, B) as tool::sequence::Cons>
       = help:   <&'a (A, B) as tool::sequence::Cons>
       = help:   <&'a mut (A, B) as tool::sequence::Cons>
       = note: required because of the requirements on the impl of `tool::sequence::Second` for `&&(&str, &std::ops::Fn(i32) -> bool)`
       = note: required by `tool::second`
    

    ah, so I need to use the git version of tool

    the newest version has the correct impl, so when I do tool = { git = "https://github.com/Stebalien/tool-rs" } it actually compiles

    so now the issue is:

    filter(apply(|c| second(c), i)) works and filter(apply(second, i)) doesn't

    and I assume the author of tool can't fix this one because of the higher rank lifetime issue?

    iopq at 2016-10-23 01:36:48

  3. Oh, so I didn’t check the issue closely enough.

    So… even with the change to the tool crate, second still only implements Fn*<(_,)>, whereas in your apply you have F: FnMut(&B) -> G bound (which is equivalent to F: for<'r> FnMut(&'r B) -> G). These will never match as I’ve already explained in the comment above.

    Changing your apply to the code below (with reasoning comments) fixes your problem.

    fn apply<A, B, C, F, G>(mut f: F, a: A) 
    -> impl FnMut(&B) -> C // must still be `for<'r> impl FnMut(&'r B) -> C`, because that’s what filter requires
             where F: FnMut(B) -> G, // must not be `for<'r> FnMut(&'r B) -> G`, because regular functions do not implement it
                   G: FnMut(A) -> C,
                   B: Copy, // for dereferencing
                   A: Clone {
    
        move |b| f(*b)(a.clone()) // this must do any bridging necessary to satisfy the requirements between filter and regular functions
    }
    

    The error still (and even more so, now) seems totally correct (although kind-of opaque) to me.

    Simonas Kazlauskas at 2016-10-23 03:21:22

  4. Ah, that does fix my issue.

    The error is correct, but I still don't understand it that well.

    for all possible lifetimes 'r (∀, for all, any, arbitrary), unrelated to any other lifetime. The function only implements std::ops::FnMut(_,)>

    How would that function implement such a thing?

    iopq at 2016-10-23 08:26:54

  5. How would that function implement such a thing?

    I have no idea, but I have a feeling that you cannot implement traits for regular functions, so the only way to deal with the issue is to tweak bounds.

    Simonas Kazlauskas at 2016-10-23 14:06:39

  6. The error message for this problem has changed to:

    error[E0308]: mismatched types
      --> src/lib.rs:52:17
       |
    52 |         .filter(apply(second, i))
       |                 ^^^^^ one type is more general than the other
       |
       = note: expected type `std::ops::FnOnce<(&&(&str, &dyn std::ops::Fn(i32) -> bool),)>`
                  found type `std::ops::FnOnce<(&&(&str, &dyn std::ops::Fn(i32) -> bool),)>`
    

    While this is wrong or at least incomplete, it would be at least much more comprehensible. I think this bug can be closed. The problem seems to be a duplicate of #41078.

    Christoph Müller at 2020-06-25 13:54:12