If wrap in `Option`, then `implementation of ... is not general enough`

Hello, is it okay that rust cannot understand such a basic use case for HRTB?

playground

struct Bar<'a>(&'a i32);
struct Baz<'a>(&'a i32);

fn foo(_: Option<impl for<'a> Fn(Baz<'a>) -> Bar<'a>>) {}
fn foo2(_: impl for<'a> Fn(Baz<'a>) -> Bar<'a>) {}

fn main() {
    foo(Some(|_| unreachable!()));
    foo2(|_| unreachable!());
}
   Compiling playground v0.0.1 (/playground)
error: implementation of `FnOnce` is not general enough
  --> src/main.rs:11:5
   |
11 |     foo(Some(|_| unreachable!()));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(Baz<'2>) -> Bar<'_>` must implement `FnOnce<(Baz<'1>,)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(Baz<'2>,)>`, for some specific lifetime `'2`

error: could not compile `playground` (bin "playground") due to 1 previous error```

Inference is quirky with closures and mostly work only with a few hardcoded situations (wrapping in Option isn't one of them), this is well known.

That is why we're getting closure_lifetime_binder:

foo(Some(for<'a> |_: Baz<'a>| -> Bar<'a> { unreachable!() }));

Except this feature is forgotten in oblivion for some reason. I asked for a status update on both githu and zulip, but unfortunately it seems no one has the bandwidth to reply.

The feature in its current form doesn't address a problem case that keeps growing:[1] unnameable types, especially return types, and particularly if they capture. In fact I believe it only works for cases that already have a workaround on stable.

It could still be future compatible with something more comprehensive, but I wonder if it's stalled in part because it falls short of what is ultimately needed.


  1. async fn, RPIT, RPITIT, gen fn, ... ↩︎

There's a workaround to get the for<'_> type hint for closures:

fn typehint<F>(f: F) -> F where F: for<'a> FnOnce(Baz<'a>) -> Bar<'a> { 
    f 
}

fn main() {
    foo(Some(typehint(|_| unreachable!())));
}

Keep in mind that creating None of this type will be super difficult anyway, because None of impl Trait is ambiguous, and there's no closure to infer the type from. You may want to provide two methods like set_callback() and clear_callback() instead of using Option.

1 Like

It can be specified with type_alias_impl_trait, but it requires another unstable feature and is clunky.

If you can solve this with TAIT I'd love to see it.

That's not the same problem. I talked about incorrect closure inference. There is something we cannot really express today (without AsyncFn), a generic future that borrows from a parameter.

It works with type erasure. Unless there's something like a soundness bug I'm unaware of which is prevented by type erasure, and this is an intentional compilation failure unsoundness,[1] the only reason it doesn't work is the incorrect closure inference.


  1. which all seems very unlikely ↩︎

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.