Need help on this HRTB use case

I defined a trait

pub trait Consumer
{
    fn consume(&self, item: (), state: &mut bool);
}

And I want it to be able to convert from a closure, so I wrote

impl<F> Consumer for F
where
    F: Fn((), &mut bool),
{
    fn consume(&self, item: (), state: &mut bool) {}
}
impl<'a, F> From<&'a F> for &'a dyn Consumer
where
    F: Fn((), &mut bool),
{
    fn from(f: &'a F) -> Self {
        f
    }
}

It seems good. Then I wrote a function to actually use it

fn foo<'a, 'b>(consumer: &'a dyn Consumer, state: &'b mut bool) {
    //Logic logic logic...
}

Finally I run a test case to use that function

fn test(){
    let mut state: bool = false;
    let c = &|_item, _state| {  };
    foo(c.into(), &mut state);
}

Now the compiler complains:

error[E0308]: mismatched types
  --> src/lib.rs:28:11
   |
28 |     foo(c.into(), &mut state);
   |           ^^^^ one type is more general than the other
   |
   = note: expected type `for<'r> Fn<((), &'r mut bool)>`
              found type `Fn<((), &mut bool)>`

error: aborting due to previous error

I think I understand the message; but how to fix it?

Playground link

Closure parameters don't get to be higher-order-promoted when their type is inferred, so you need to avoid having the type of the _state param be inferred (at least the part carrying a lifetime parameter):

fn test(){
    let mut state: bool = false;
-   let c = &|_item, _state| {  };
+   let c = &|_item, _state: &mut _| {  };
    foo(c.into(), &mut state);
}
3 Likes

Ahh. Thanks. I remember one or two years ago it was not the case. At that moment the HRTB were automatically derived but there are some ICEs related to it. I was one of those bug reporters. Then I stopped Rust for a while. Now when I am back it seems they fixed the issue by reduced ergonomic a little bit.

It is a good trade off though; having to annotate a type is better than ICE.

1 Like

Well, to be fair, my previous post was a bit too general and thus not totally correct: the signature of a closure can be affected by it being given to a function with specific trait requirements.

  • The best practical example of it is that if you want a Fn(&()) -> &() closure, for instance, the only way to express that signature is by funnelling the closure through some fn identity<F : FnOnce(&()) -> &()> (f: F) -> F { f } helper.

    FWIW, that would also be a solution to your problem.

In your case, however, you are neither using an explicit Fn bound, nor an explicit dyn Fn… coercion (which also allows to imbue the closure with the right higher-order-ness). Instead, you have gone though an auxiliary / intermediary trait, and that's what breaks higher-order promotion, and has always done it (example with 1.0.0 Rust).

In a way, it is the one counter-example for the DIY / hacked trait alias pattern. If we forget about the higher-order issue, even type inference gets confused by an intermediate trait;

fn expects_fn_string (_: impl FnOnce(String)) {}

expects_fn_string(|s| { s.into_boxed_str(); }); // OK, method resolved thanks to knowing that `s: String`

trait Alias : FnOnce(String) {}
impl<F : ?Sized> Alias for F where Self : FnOnce(String) {}

fn expects_fn_string_2 (_: impl Alias) {}
expects_fn_string_2(|s| { s.into_boxed_str(); }); // Error, can't call a method on an unknown type
3 Likes