`Fn` closure with elided higher-ranked lifetimes rejected as "not general enough" through a `[F; 1]` wrapper
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
Code
This program compiles - it is rustc's own UI test tests/ui/closure-expected-type/issue-24421.rs (directive //@ check-pass), a closure whose argument types are inferred from an Fn bound with elided (higher-ranked) lifetimes:
fn test<F: Fn(&u64, &u64)>(f: F) {}
fn main() {
test(|x, y| {});
}
Wrapping the f parameter in a one-element array [F; 1] (destructured in the body with let [f] = _f;) and wrapping the call-site closure in [ ... ] makes inference fail:
fn test<F: Fn(&u64, &u64)>(_f: [F; 1]) {
let [f] = _f;
}
fn main() {
test([|x, y| {}]); // error: implementation of `Fn` is not general enough
}
Expected
Both versions compile. The bound F: Fn(&u64, &u64) should behave the same as F: for<'a, 'b> Fn(&'a u64, &'b u64) (writing the bound that explicit way compiles identically) - a higher-ranked trait bound requiring F to implement Fn for any pair of lifetimes on the arguments. The rewritten program gives the compiler the same information: the argument has expected type [F; 1], so its single element has expected type F, still carrying the bound F: for<'a, 'b> Fn(&'a u64, &'b u64). The array literal adds one layer of structure but no ambiguity. The closure |x, y| {} accepts any (&u64, &u64) pair regardless of lifetime, so both programs are equivalent and both should compile.
Actual
The array-wrapped version is rejected:
error: implementation of `Fn` is not general enough
--> src/main.rs:6:5
|
6 | test([|x, y| {}]);
| ^^^^^^^^^^^^^^^^^ implementation of `Fn` is not general enough
|
= note: closure with signature `fn(&'2 u64, &u64)` must implement `Fn<(&'1 u64, &u64)>`, for any lifetime `'1`...
= note: ...but it actually implements `Fn<(&'2 u64, &u64)>`, for some specific lifetime `'2`
error: implementation of `Fn` is not general enough
--> src/main.rs:6:5
|
6 | test([|x, y| {}]);
| ^^^^^^^^^^^^^^^^^ implementation of `Fn` is not general enough
|
= note: closure with signature `fn(&u64, &'2 u64)` must implement `Fn<(&u64, &'1 u64)>`, for any lifetime `'1`...
= note: ...but it actually implements `Fn<(&u64, &'2 u64)>`, for some specific lifetime `'2`
error: implementation of `FnOnce` is not general enough
--> src/main.rs:6:5
|
6 | test([|x, y| {}]);
| ^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
|
= note: closure with signature `fn(&'2 u64, &u64)` must implement `FnOnce<(&'1 u64, &u64)>`, for any lifetime `'1`...
= note: ...but it actually implements `FnOnce<(&'2 u64, &u64)>`, for some specific lifetime `'2`
error: implementation of `FnOnce` is not general enough
--> src/main.rs:6:5
|
6 | test([|x, y| {}]);
| ^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
|
= note: closure with signature `fn(&u64, &'2 u64)` must implement `FnOnce<(&u64, &'1 u64)>`, for any lifetime `'1`...
= note: ...but it actually implements `FnOnce<(&u64, &'2 u64)>`, for some specific lifetime `'2`
error: aborting due to 4 previous errors
Wrapper variants
The failure is not universal across indirect wrappers. Each row keeps the F: Fn(&u64, &u64) bound and an empty body, varying only how F is wrapped:
| Wrapper | Call site | Result |
|---|---|---|
| none (baseline) | test(|x, y| {}) |
yes - compiles |
&F |
test(&|x, y| {}) |
yes - compiles |
(F,) |
test((|x, y| {},)) |
yes - compiles |
[F; 1] |
test([|x, y| {}]) |
no - not general enough |
Option<F> |
test(Some(|x, y| {})) |
no - not general enough |
The direct argument, a reference, and a tuple field compile; the array literal and the enum variant constructor (Some(...)) produce "not general enough." This is the same set of working forms (F, &F, (F,)) observed for a related E0282 inference split with the same [F; 1] wrapper, filed separately.
Partial annotation
Annotating a subset of the closure parameters fixes only those parameters:
| Call site | Result |
|---|---|
test([|x, y| {}]) |
no - both x and y fail |
test([|x:&u64, y:&u64| {}]) |
yes - compiles |
test([|x:&u64, y| {}]) |
no - only y fails |
test([|x, y:&u64| {}]) |
no - only x fails |
Annotating a closure parameter with an explicit &u64 makes that parameter compile; an unannotated parameter in the same closure still fails. The failure therefore appears only for a parameter whose reference type has no source other than the Fn bound reaching the closure through the wrapper.
Notes
The same "not general enough" error appears in existing issues #70263, #99991, and #87079.
But no existing issue combines the array-literal surface trigger with this error.
Meta
rustc 1.96.0 (ac68faa20 2026-05-25)
binary: rustc
commit-hash: ac68faa20c58cbccd01ee7208bf3b6e93a7d7f96
commit-date: 2026-05-25
host: aarch64-apple-darwin
release: 1.96.0
LLVM version: 22.1.2
Tested on stable 1.96.0 only.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the array-wrapped and baseline examples with rustc 1.96, then compare the wrapper variants and partial annotations described in the issue. Start with the existing passing test tests/ui/closure-expected-type/issue-24421.rs and related issues #70263, #99991, and #87079; done means the array-wrapped closure compiles without weakening the higher-ranked bound.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100