rust-lang / rust-lang/rust

fn-item to fn-pointer coercion fails with E0271 when the fn item is wrapped with array + closure

Offen
#158,653 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

C-bug needs-triage
Vorherrschende Sprache
Rust
Sterne
119k
Forks
16.2k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

Code

This program compiles and runs - it is rustc's own UI test tests/ui/functions-closures/fn-bare-spawn.rs:

fn spawn<T: Send>(val: T, f: fn(T)) { f(val); }

fn f(i: isize) { assert_eq!(i, 100); }

pub fn main() {
    spawn(100, f); // fn item `f` is coerced to the fn pointer `fn(isize)` at the argument site
}

The argument f is an fn item of fn(isize) {f}; it is coerced to the fn pointer fn(isize) because the parameter's expected type is fn(T).
This coercion is driven by the expected type.

Now route the same fn item through a closure F: FnOnce() -> fn(T) and wrap that closure in a one-element array [F; 1] (destructured in the body with let [f0] = _f0;, call-site closure wrapped in [ … ]).
The expected type fn(T) is still there but inference no longer performs the coercion:

fn spawn<T: Send, F: FnOnce() -> fn(T)>(val: T, _f0: [F; 1]) {
    let [f0] = _f0;
    let f = f0();
    f(val);
}

fn f(i: isize) { assert_eq!(i, 100); }

pub fn main() {
    spawn(100, [move || f]); // <-- error[E0271]
}
Expected

Both versions compile.

The compiler has exactly the same information, as the expected type still reaches f:

  1. the array argument has expected type [F; 1], so its sole element has expected type F.
  2. F still carries the bound F: FnOnce() -> fn(T), which fixes the closure's return type to fn(T).
  3. so the closure body f has expected type fn(T).
Actual

The array-wrapped version is rejected with a type mismatch between the fn item and the fn pointer it should have been coerced to:

error[E0271]: expected `{closure@src/main.rs:10:17}` to return `fn({integer})`, but it returns `fn(isize) {f}`
  --> src/main.rs:10:25
   |
10 |     spawn(100, [move || f]);
   |     -----       ------- ^ expected fn pointer, found fn item
   |     |           |
   |     |           this closure
   |     required by a bound introduced by this call
   |
   = note: expected fn pointer `fn({integer})`
                 found fn item `fn(isize) {f}`
note: required by a bound in `spawn`
  --> src/main.rs:1:34
   |
 1 | fn spawn<T: Send, F: FnOnce() -> fn(T)>(val: T, _f0: [F; 1]) {
   |                                  ^^^^^ required by this bound in `spawn`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0271`.

The closure is inferred to return the fn item's own type fn(isize) {f} instead of being coerced to the expected fn(isize) pointer - i.e. the expected return type fn(T) did not reach the closure body through the array element.

Testing with different wrapper types
// direct F - compiles
fn spawn<T: Send, F: FnOnce() -> fn(T)>(val: T, _f0: F)    { let f = _f0();     f(val); }
// (F,) - compiles
fn spawn<T: Send, F: FnOnce() -> fn(T)>(val: T, _f0: (F,)) { let f = (_f0.0)(); f(val); }
// [F; 1] - fails E0271
fn spawn<T: Send, F: FnOnce() -> fn(T)>(val: T, _f0: [F; 1]) { let [f0] = _f0; let f = f0(); f(val); }

fn f(i: isize) { assert_eq!(i, 100); }
// main, respectively:
//   spawn(100, move || f);
//   spawn(100, (move || f,));
//   spawn(100, [move || f]);

A reference &F also compiles (with the bound relaxed to F: Fn() -> fn(T), since a FnOnce cannot be called through &F).

With explicit type annotations, the wrapped form compiles

The same [F; 1]-wrapped form compiles once the closure's return type is supplied explicitly, by either of two routes:

// concrete array element type --> the coercion fires through the array literal + closure body
fn f(i: isize) { assert_eq!(i, 100); }
fn main() {
    let _x: [fn() -> fn(isize); 1] = [move || f]; // compiles (the coercion fires through the annotation)
    let _ = _x;
}
// explicit closure return annotation supplies the expected type by hand
fn spawn<T: Send, F: FnOnce() -> fn(T)>(val: T, _f0: [F; 1]) {
    let [f0] = _f0; let f = f0(); f(val);
}
fn f(i: isize) { assert_eq!(i, 100); }
fn main() {
    spawn(100isize, [move || -> fn(isize) { f }]); // compiles
}

Both compile. The failure appears only when the closure's return type has no source other than being inferred through the [F; 1] element from the callee bound.

Notes

Possibly related to #157468, another [F; 1]-wrapping case involving a callee Fn/FnOnce bound, which surfaces as E0282. I am filing this as a separate issue because the symptom differs - the error code here is E0271 rather than E0282.

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.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne damit, die minimalen direkten, Tupel- und [F; 1]-Beispiele zu reproduzieren und sie mit tests/ui/functions-closures/fn-bare-spawn.rs zu vergleichen. Verwende die Fälle mit expliziten Annotationen, um das fehlende Coercion-Verhalten zu isolieren. Als erledigt gilt die Aufgabe, wenn die in ein Array eingeschlossene Closure wie die direkten und Tupel-Formen kompiliert, mit Regressionstests für den Fall E0271.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
rust
Bereich
compilers
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Ruhig
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
45/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.