rust-lang / rust-lang/rust

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

Aperta
#158,653 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

C-bug needs-triage
Lingua principale
Rust
Stelle
119k
Fork
16.2k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

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.

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia riproducendo gli esempi minimi diretto, tupla e [F; 1], confrontandoli con tests/ui/functions-closures/fn-bare-spawn.rs. Usa i casi con annotazione esplicita per isolare il comportamento di coercion mancante. Il lavoro è completato quando la closure racchiusa in un array compila come le forme diretta e tupla, con una copertura di regressione per il caso E0271.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
rust
Ambito
compilers
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Tranquilla
Chiarezza
Abbastanza chiara
Idoneità per principianti
45/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.