Salta ai contenuti

Errore Rust E0631: type mismatch in closure arguments (tipo dei parametri)

E0631 significa che la tua closure ha il numero giusto di parametri, ma almeno uno ha il tipo sbagliato. Nove volte su dieci la differenza è un solo &: hai scritto |x: i32|, ma l’iteratore ti presta un &i32. Spesso la correzione più veloce è cancellare l’annotazione di tipo e lasciare che Rust lo capisca da solo. Questa pagina mostra prima quella soluzione, poi spiega da dove arriva il &, così la prossima volta te lo aspetti. Per gli altri errori di questa serie, vedi Errori del compilatore Rust.

error[E0631]: type mismatch in closure arguments
--> src/main.rs:3:43
|
3 | let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ items are &i32
| ^^^ -------- found signature defined here
| |
| expected due to this
|
= note: expected closure signature `fn(&{integer}) -> _`
found closure signature `fn(i32) -> _`
note: required by a bound in `map`
help: consider adjusting the signature so it borrows its argument
|
3 | let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect(); // ❌ items are &i32
| +

Il compilatore ti dice: «tipi non corrispondenti negli argomenti della closure». Ti mostra la firma che si aspettava, quella che ha trovato, e ti suggerisce di far ricevere alla closure un riferimento.

Ecco il programma che lo provoca:

fn main() {
let prices = vec![120, 45, 300];
let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ items are &i32
println!("{:?}", doubled);
}

(Il commento dice: gli elementi sono &i32.)

Subito dopo, rustc stampa anche un secondo errore, più lungo: the method `collect` exists for struct `Map<...>`, but its trait bounds were not satisfied (E0599). È solo un effetto collaterale. Risolvi E0631 e sparisce.

Le due righe note sono il cuore del messaggio. Leggile come «il metodo voleva questo, tu hai scritto quello»:

  • expected fn(&{integer}) -> _: map chiamerà la tua closure con un riferimento a un intero. {integer} vuol dire «un tipo intero che Rust non ha ancora fissato», e _ vuol dire «qualsiasi tipo di ritorno».
  • found fn(i32) -> _: la tua closure dice di ricevere un i32 per valore.

Mettile una accanto all’altra e l’unica differenza è il &. La riga help: mostra la correzione di un solo carattere.

Pensa al guardaroba di un teatro. Lasci il cappotto e ricevi un tagliando. .iter() funziona allo stesso modo: non ti dà gli elementi, ti dà dei tagliandi per raggiungerli (i riferimenti). Una closure che pretende il cappotto in persona non accetta un tagliando.

Ogni metodo degli iteratori decide esattamente quale tipo passare alla tua closure, e quel tipo dipende da come hai creato l’iteratore:

  • v.iter() presta ogni elemento: &T.
  • v.into_iter() (su un Vec che possiedi) cede ogni elemento: T.
  • filter e find ti prestano l’elemento un’altra volta, quindi sopra a .iter() ottieni &&T.
  • sort_by ti presta due elementi: &T e &T.

Quando ometti i tipi dei parametri, Rust li prende dal metodo, quindi vanno sempre bene. Quando scrivi un tipo tu, Rust ti prende in parola e lo controlla. Non copia il valore di nascosto e non trasforma un riferimento in un valore posseduto, perché cambierebbe il comportamento del tuo codice (e per una String una copia costa memoria e tempo). La lezione Iteratori mostra i tre modi per ottenere un iteratore, e Prestito (borrowing) spiega i riferimenti.

Prova questa per prima. I parametri delle closure non hanno quasi mai bisogno di un tipo: Rust sa esattamente cosa passano map, filter e sort_by. Cancella : i32 e il codice compila:

fn main() {
let prices = vec![120, 45, 300];
let ages = vec![12, 34, 17, 50];
let mut scores = vec![42, 7, 99, 15];
// nessuna annotazione di tipo: Rust usa ciò che l'iteratore dà davvero
let doubled: Vec<i32> = prices.iter().map(|p| p * 2).collect();
let adults: Vec<&i32> = ages.iter().filter(|age| **age >= 18).collect();
scores.sort_by(|a, b| b.cmp(a));
println!("{:?}", doubled);
println!("{:?}", adults);
println!("{:?}", scores);
}

Output:

[240, 90, 600]
[34, 50]
[99, 42, 15, 7]
▶ Provalo tu

p * 2 funziona su un &i32 perché gli operatori numerici di Rust accettano anche i riferimenti. In filter, age è un &&i32, quindi **age segue entrambi i riferimenti fino al numero. I confronti come b.cmp(a) funzionano direttamente sui riferimenti.

Soluzione 2: scrivi il tipo che il metodo passa davvero

Sezione intitolata “Soluzione 2: scrivi il tipo che il metodo passa davvero”

Scegli questa strada quando vuoi tenere il tipo in vista, per esempio per rendere più leggibile una catena lunga. Segui l’help: di rustc e aggiungi il &. Puoi anche mettere il & nel pattern: |&p| scompone il riferimento e ti dà una semplice copia del numero (funziona con i tipi Copy come gli interi):

fn main() {
let prices = vec![120, 45, 300];
let ages = vec![12, 34, 17, 50];
let mut scores = vec![42, 7, 99, 15];
// scrivi i tipi veri: & per .iter(), && dentro filter
let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect();
let adults: Vec<&i32> = ages.iter().filter(|age: &&i32| **age >= 18).collect();
scores.sort_by(|a: &i32, b: &i32| b.cmp(a));
// oppure copia fuori il numero con un pattern &
let tax: Vec<i32> = prices.iter().map(|&p| p / 10).collect();
println!("{:?}", doubled);
println!("{:?}", adults);
println!("{:?}", scores);
println!("{:?}", tax);
}

Output:

[240, 90, 600]
[34, 50]
[99, 42, 15, 7]
[12, 4, 30]
▶ Provalo tu

Nota la differenza: |p: &i32| vuol dire «p è un riferimento», mentre |&p| vuol dire «ricevo un riferimento, dammi quello che c’è dietro».

Soluzione 3: cambia quello che l’iteratore distribuisce

Sezione intitolata “Soluzione 3: cambia quello che l’iteratore distribuisce”

Scegli questa strada quando vuoi davvero i valori, non i riferimenti. Hai due modi per ottenerli:

  • .iter().copied() trasforma ogni &i32 in un i32. Usalo per piccoli tipi Copy come numeri, char e bool. (.cloned() fa lo stesso per tipi come String, clonandoli.)
  • .into_iter() su un Vec distribuisce gli elementi stessi. Dopo, il Vec è consumato, quindi sceglilo quando non ti serve più.
fn main() {
let prices = vec![120, 45, 300];
// .copied() trasforma gli elementi &i32 in elementi i32
let doubled: Vec<i32> = prices.iter().copied().map(|p: i32| p * 2).collect();
println!("{:?}", doubled);
// .into_iter() su un Vec distribuisce gli elementi stessi (e consuma il Vec)
let names = vec![String::from("ferris"), String::from("corro")];
let loud: Vec<String> = names.into_iter().map(|name: String| name.to_uppercase()).collect();
println!("{:?}", loud);
}

Output:

[240, 90, 600]
["FERRIS", "CORRO"]
▶ Provalo tu

Se dopo il ciclo ti serve ancora names, tieni .iter() e ricevi un &String. Spostare elementi fuori da un Vec che hai solo preso in prestito è un altro errore, E0507.

Soluzione 4: fai ricevere a una funzione con nome ciò che dà l’iteratore

Sezione intitolata “Soluzione 4: fai ricevere a una funzione con nome ciò che dà l’iteratore”

Scegli questa strada quando passi una funzione per nome, come .map(shout). In questo caso il messaggio dice «function» invece di «closure»:

error[E0631]: type mismatch in function arguments
--> src/main.rs:7:51
|
1 | fn shout(word: &String) -> String {
| --------------------------------- found signature defined here
...
7 | let loud: Vec<String> = words.into_iter().map(shout).collect();
| --- ^^^^^ expected due to this
| |
| required by a bound introduced by this call
|
= note: expected function signature `fn(&str) -> _`
found function signature `fn(&String) -> _`

Gli elementi sono &str, ma shout vuole un &String. Una buona abitudine: le funzioni che leggono solo del testo ricevano &str. Così accettano letterali stringa, &String e altro ancora. Quando l’iteratore aggiunge un & in più (come fa filter), avvolgi la chiamata in una piccola closure. In una normale chiamata di funzione Rust toglie da solo i & di troppo (si chiama deref coercion), ma non può farlo quando passi la funzione per nome:

// ricevi &str: funziona con letterali stringa, &String e altro
fn shout(word: &str) -> String {
word.to_uppercase()
}
fn is_long(word: &str) -> bool {
word.len() > 4
}
fn main() {
let words = vec!["crab", "rustacean", "ferris"];
// gli elementi sono &str, quindi shout si può passare per nome
let loud: Vec<String> = words.iter().copied().map(shout).collect();
println!("{:?}", loud);
// filter dà &&str: una piccola closure lascia a Rust la conversione
let long: Vec<&str> = words.iter().copied().filter(|w| is_long(w)).collect();
println!("{:?}", long);
}

Output:

["CRAB", "RUSTACEAN", "FERRIS"]
["rustacean", "ferris"]
▶ Provalo tu

Qui le parole restano in inglese perché is_long conta i byte con .len(). Trovi di più su &str e String in Stringhe.

Ordinare con parametri tipizzati. scores.sort_by(|a: i32, b: i32| b.cmp(&a)) dà E0631 con expected closure signature `for<'a, 'b> fn(&'a {integer}, &'b {integer}) -> _`. La parte for<'a, 'b> dice solo «per qualsiasi prestito». Soluzione: |a, b| b.cmp(a). Vedi Ordinamento.

Voci di una mappa con tipi posseduti. stock.iter().map(|(name, count): (String, i32)| ...) su una HashMap<String, i32> dà expected closure signature `fn((&String, &_)) -> _`. .iter() su una mappa presta sia la chiave sia il valore. Soluzione: togli l’annotazione, oppure scrivi (&String, &i32).

Una tua funzione generica. Se fn apply<F: Fn(i32) -> i32>(f: F, x: i32) riceve una closure |x: f64| x / 2.0, rustc segnala expected closure signature `fn(i32) -> _` e found closure signature `fn(f64) -> _`, indicando il vincolo. Soluzione: fai ricevere i32 alla closure (e converti dentro, x as f64 / 2.0), oppure cambia il vincolo se intendevi davvero f64. Vedi Trait delle closure.

for_each con String. words.iter().for_each(|w: String| ...) su un Vec<&str> si aspetta fn(&&str). Un &str non è una String. Soluzione: |w|, oppure |w: &&str|.

Verifica veloce

1. let v = vec![1, 2, 3]; Che tipo riceve la closure in v.iter().map(|x| ...)?

2. La nota dice expected closure signature fn(&{integer}) -> _, found closure signature fn(i32) -> _. Qual è la correzione più veloce?

3. Cosa fa |&p| p * 2 dentro prices.iter().map(...)?

Punteggio: 0 / 3