Hi!
I recently discovered a problem in my code which I don't know how to escape.
Basically, my structure behaves similar to this:
// this makes an anonymous struct which takes the `T`
fn printer<T: Debug>() -> impl FnOnce(T) {
|val| { println!("{val:?}"); }
}
fn take_binded(_: impl for<'a> FnOnce(&'a i32)) { .. }
Now writing the printer without the generic T makes it able to being passed into take_binded.
However, because there's the generic involved, it tries to collapse the lifetime and I instead get
error: implementation of `FnOnce` is not general enough
--> src/main.rs:5:5
|
5 | take_binded(printer());
| ^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
|
= note: `impl FnOnce(&'2 i32)` must implement `FnOnce<(&'1 i32,)>`, for any lifetime `'1`...
= note: ...but it actually implements `FnOnce<(&'2 i32,)>`, for some specific lifetime `'2`
As I've already played around with some basic trait shenanigans to overcome this, I don't think it's possible to make this work (except there's a suuuper magic trick for this).
Tho I wanted to ask if there's any reason logical-wise which should disallow this or if there just wasn't yet made the effort to "track bindings" of lifetimes with generics.
Thanks 
PS: for playing around with the issue playground
1 Like
T represents a single type, and there is no single &i32 type for all lifetimes.
Rust doesn't have "generic type constructors" to allow something like
// vvvvv Some type constructor generic over... whatever.
fn printer<T<..>: Debug>() -> impl FnOnce(T<..>)
// (Lifetimes are all that a higher-ranked `FnOnce` could support so far.)
If we get something like that, I doubt it would be invisible (look like your OP / be always or implicitly present in generics). If we get something close to it, it won't be for many years. I doubt it's a simple feature.
The closest serious proposals that come to mind are generic variadics, but I haven't checked to see how applicable those are to this use case.
By the way, a workaround for your playground is
test(|i| { printer()(i) });
test(|i| unbind(|_| {})(i));
I thought of another possible future feature: Opaques which are higher-ranked over types.
// Perhaps some day we'll get this and it will also meet a
// `for<'a> FnOnce(&'a i32)` bound
fn printer() -> impl for<T: Debug> FnOnce(T) {
|val| { println!("{val:?}"); }
}
Probably requires a more general "higher-ranked over types" support. There's some experimental support for non-lifetime binders, but the experiment doesn't cover higher-ranked types or opaques, just where clauses (think GAT bounds).
You an also change printer to return impl for<'a> FnOnce<&'a T> or relax the for<'a> on F in take_binded, so that it simply needs to work for a single lifetime (i.e., fn take_binded<'a, F: FnOnce(&'a i32)>(_ :F){}. Those workarounds may cause other issues in the actual code though.
1 Like
Those are all great ideas, but sadly I can't say that T will always be of type &'_ T and even tho I already use the non_lifetime_binder feature, I don't think that's where I'm heading as my real code doesn't use FnOnce but my own trait.
However I've now come up with a "trampoline" implementation of the trait which explicitly covers the lifetime in the type and then refers to that impl everywhere.