"implementation of `FnOnce` is not general enough"

I want to use rusqlite hooks, and using:

  conn.update_hook(Some(|action, _dbname, tabler, rowid| {
    let g = a.lock();
  }));

yields:

error: implementation of `FnOnce` is not general enough
   --> src/db.rs:399:8
    |
399 |   conn.update_hook(Some(|action, _dbname, table, rowid| {
    |        ^^^^^^^^^^^ implementation of `FnOnce` is not general enough
    |
    = note: closure with signature `fn(Action, &'2 str, &str, i64)` must implement `FnOnce<(Action, &'1 str, &str, i64)>`, for any lifetime `'1`...
    = note: ...but it actually implements `FnOnce<(Action, &'2 str, &str, i64)>`, for some specific lifetime `'2`

error: implementation of `FnOnce` is not general enough
   --> src/db.rs:399:8
    |
399 |   conn.update_hook(Some(|action, _dbname, table, rowid| {
    |        ^^^^^^^^^^^ implementation of `FnOnce` is not general enough
    |
    = note: closure with signature `fn(Action, &str, &'2 str, i64)` must implement `FnOnce<(Action, &str, &'1 str, i64)>`, for any lifetime `'1`...
    = note: ...but it actually implements `FnOnce<(Action, &str, &'2 str, i64)>`, for some specific lifetime `'2`

The fix is:

  conn.update_hook(Some(|action, _dbname: &str, table: &str, rowid| {
    let g = a.lock();
  }));

(Note explicit &str on _dbname and table)

.. but why?

I think that's a clumsy way of the compiler saying the closure borrows its context, but it's not allowed to.

Try making it a move closure:

conn.update_hook(Some(move |action, _dbname,
1 Like

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

5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.