"implementation of `FnOnce` is not general enough"

Minimal repro:

fn foo (_: Option<impl FnOnce(&str)>)
{}

fn main ()
{
    foo(Some(|_| ()));
}

Indeed, doing:

-   foo(Some(|_| ()));
+   foo(Some(|_: &str| ()));

does fix the problem.

The issue stems from how closures, type inference, and higher-order signatures play together: for historical reasons, closures favor type inference / are reluctant to get higher-order promoted. In fact, they "rarely" do, except for one special hard-coded case which was added to have at least one way to get higher-order closures: when a literal closure expression is fed to a function which carries explicit Fn… bounds on that very function arg.
It's one weird situation / quirk of the language which allows those bounds to "retroact" on the nature of the Fn…-ness of the closure and tries to imbue it with a higher-order signature.

But in this minimal repro, as well as with ::rusqlite's hooks module feature-gated with an eponymous feature (cc @thomcc: that features is missing from the docs.rs batch and thus does not appear on the official docs), the closure parameter is not exactly an F, but an Option<F>, and the closure is thus given as a Some(…).

That seems to be enough to throw a wrench in the retroactive-higher-order-signature-imbuing works, at least when the type of the arg carrying the (to-be-higher-order) lifetime parameter is fully inferred. And it looks like providing a type annotation early (before type inference) that makes it obvious that there is a (promotable) lifetime parameter there does help nudge Rust into making the signature properly higher-order.


See also:

for more info and a helper macro to deal with more advanced closure signatures

7 Likes