Ir al contenido

Error E0631 de Rust: type mismatch in closure arguments (tipos de argumentos)

E0631 significa que tu closure tiene el número correcto de parámetros, pero al menos uno tiene el tipo equivocado. Nueve de cada diez veces, la diferencia es un solo &: escribiste |x: i32|, pero el iterador te presta un &i32. La solución más rápida suele ser borrar la anotación de tipo y dejar que Rust lo averigüe. Esta página te muestra primero esa solución y después explica de dónde sale el &, para que la próxima vez lo veas venir. Para los demás errores de esta serie, mira Errores del compilador de 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(); // ❌ los elementos son &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(); // ❌ los elementos son &i32
| +

“Type mismatch in closure arguments” quiere decir “los tipos de los argumentos de la closure no coinciden”. El help te sugiere ajustar la firma para que la closure tome prestado su argumento, y marca con + dónde va el &.

Este es el programa que lo produce:

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

rustc también imprime justo después un segundo error, más largo: the method `collect` exists for struct `Map<...>`, but its trait bounds were not satisfied (E0599), es decir, “el método collect existe, pero no se cumplen sus restricciones de traits”. Ese es solo un efecto secundario. Arregla E0631 y desaparece.

Las dos líneas note son el corazón del mensaje. Léelas como “el método quería esto, tú escribiste aquello”:

  • expected fn(&{integer}) -> _ (se esperaba): map va a llamar a tu closure con una referencia a un entero. {integer} significa “algún tipo entero que Rust todavía no ha fijado”, y _ significa “cualquier tipo de retorno”.
  • found fn(i32) -> _ (se encontró): tu closure dice que recibe un i32 por valor.

Ponlas una al lado de la otra y la única diferencia es el &. La línea help: muestra la solución de un solo carácter.

Piensa en el guardarropa de un teatro. Entregas tu abrigo y te dan un tique. .iter() funciona igual: no te entrega los elementos, te entrega tiques para ellos (referencias). Una closure que insiste en recibir el abrigo mismo no acepta un tique.

Cada método de un iterador decide exactamente qué tipo le pasa a tu closure, y ese tipo depende de cómo creaste el iterador:

  • v.iter() presta cada elemento: &T.
  • v.into_iter() (sobre un Vec que es tuyo) regala cada elemento: T.
  • filter y find te prestan el elemento otra vez, así que encima de .iter() recibes &&T.
  • sort_by te presta dos elementos: &T y &T.

Cuando no escribes los tipos de los parámetros, Rust los rellena a partir del método, así que siempre encajan. Cuando escribes un tipo tú mismo, Rust te toma la palabra y lo comprueba. No va a copiar el valor a escondidas ni a convertir una referencia en un valor propio, porque eso cambiaría lo que hace tu código (y con un String, una copia cuesta memoria y tiempo). La lección de Iteradores muestra las tres formas de obtener un iterador, y Borrowing explica las referencias.

Elige esta primero. Los parámetros de una closure casi nunca necesitan tipo: Rust sabe exactamente qué pasan map, filter y sort_by. Borra : i32 y el código compila:

fn main() {
let prices = vec![120, 45, 300];
let ages = vec![12, 34, 17, 50];
let mut scores = vec![42, 7, 99, 15];
// sin anotaciones de tipo: Rust usa lo que el iterador da de verdad
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);
}

Salida:

[240, 90, 600]
[34, 50]
[99, 42, 15, 7]
▶ Pruébalo tú mismo

p * 2 funciona con un &i32 porque los operadores numéricos de Rust también aceptan referencias. En filter, age es un &&i32, así que **age sigue las dos referencias hasta llegar al número. Las comparaciones como b.cmp(a) funcionan directamente con referencias.

Solución 2: Escribir el tipo que el método pasa de verdad

Sección titulada «Solución 2: Escribir el tipo que el método pasa de verdad»

Elige esta cuando quieres que el tipo se vea, por ejemplo para que una cadena larga sea más fácil de leer. Sigue el help: de rustc y añade el &. También puedes poner el & en el patrón: |&p| desarma la referencia y te da una copia normal del número (esto funciona con tipos Copy como los enteros):

fn main() {
let prices = vec![120, 45, 300];
let ages = vec![12, 34, 17, 50];
let mut scores = vec![42, 7, 99, 15];
// escribe los tipos reales: & para .iter(), && dentro de 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));
// o copia el número con un patrón &
let tax: Vec<i32> = prices.iter().map(|&p| p / 10).collect();
println!("{:?}", doubled);
println!("{:?}", adults);
println!("{:?}", scores);
println!("{:?}", tax);
}

Salida:

[240, 90, 600]
[34, 50]
[99, 42, 15, 7]
[12, 4, 30]
▶ Pruébalo tú mismo

Fíjate en la diferencia: |p: &i32| significa “p es una referencia”, mientras que |&p| significa “recibo una referencia, dame lo que hay detrás”.

Solución 3: Cambiar lo que entrega el iterador

Sección titulada «Solución 3: Cambiar lo que entrega el iterador»

Elige esta cuando de verdad quieres los valores, no referencias. Hay dos formas de conseguirlos:

  • .iter().copied() convierte cada &i32 en un i32. Úsalo con tipos Copy pequeños, como números, char y bool. (.cloned() hace lo mismo con tipos como String, clonándolos).
  • .into_iter() sobre un Vec entrega los propios elementos. Después el Vec queda gastado, así que elige esta opción cuando ya no lo necesites.
fn main() {
let prices = vec![120, 45, 300];
// .copied() convierte elementos &i32 en elementos i32
let doubled: Vec<i32> = prices.iter().copied().map(|p: i32| p * 2).collect();
println!("{:?}", doubled);
// .into_iter() sobre un Vec entrega los propios elementos (y gasta el 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);
}

Salida:

[240, 90, 600]
["FERRIS", "CORRO"]
▶ Pruébalo tú mismo

Si todavía necesitas names después, quédate con .iter() y recibe &String. Intentar sacar elementos de un Vec que solo tienes prestado es otro error distinto, E0507.

Solución 4: Hacer que una función con nombre reciba lo que da el iterador

Sección titulada «Solución 4: Hacer que una función con nombre reciba lo que da el iterador»

Elige esta cuando pasas una función por su nombre, como .map(shout). Entonces el mensaje dice “function” en lugar de “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) -> _`

Los elementos son &str, pero shout quiere &String. Un buen hábito: haz que las funciones que solo leen texto reciban &str. Así aceptan literales de texto, &String y más. Cuando el iterador añade un & extra (como hace filter), envuelve la llamada en una closure corta. En una llamada normal a una función, Rust quita los & de más por ti (esto se llama deref coercion, coerción por desreferencia), pero no puede hacerlo cuando pasas la función por su nombre.

Las palabras del ejemplo se quedan en inglés porque is_long mide su longitud con .len(), que cuenta bytes:

// recibe &str: funciona con literales de texto, &String y más
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"];
// los elementos son &str, así que shout se puede pasar por su nombre
let loud: Vec<String> = words.iter().copied().map(shout).collect();
println!("{:?}", loud);
// filter da &&str: una closure pequeña deja que Rust lo convierta por ti
let long: Vec<&str> = words.iter().copied().filter(|w| is_long(w)).collect();
println!("{:?}", long);
}

Salida:

["CRAB", "RUSTACEAN", "FERRIS"]
["rustacean", "ferris"]
▶ Pruébalo tú mismo

Más sobre &str frente a String en Strings.

Ordenar con parámetros con tipo. scores.sort_by(|a: i32, b: i32| b.cmp(&a)) da E0631 con expected closure signature `for<'a, 'b> fn(&'a {integer}, &'b {integer}) -> _`. La parte for<'a, 'b> solo dice “para cualquier préstamo”. Solución: |a, b| b.cmp(a). Mira Ordenar y buscar.

Entradas de un mapa con tipos propios. stock.iter().map(|(name, count): (String, i32)| ...) sobre un HashMap<String, i32> da expected closure signature `fn((&String, &_)) -> _`. .iter() sobre un mapa presta tanto la clave como el valor. Solución: quita la anotación, o escribe (&String, &i32).

Tu propia función genérica. Si fn apply<F: Fn(i32) -> i32>(f: F, x: i32) recibe una closure |x: f64| x / 2.0, rustc informa expected closure signature `fn(i32) -> _` y found closure signature `fn(f64) -> _`, señalando la restricción. Solución: haz que la closure reciba i32 (y convierte dentro, x as f64 / 2.0), o cambia la restricción si lo que querías era f64. Mira Traits de closures.

for_each con String. words.iter().for_each(|w: String| ...) sobre un Vec<&str> espera fn(&&str). Un &str no es un String. Solución: |w|, o |w: &&str|.

Comprueba lo aprendido

1. let v = vec![1, 2, 3]; ¿Qué tipo recibe la closure en v.iter().map(|x| ...)?

2. La nota dice expected closure signature fn(&{integer}) -> _, found closure signature fn(i32) -> _. ¿Cuál es la solución más rápida?

3. ¿Qué hace |&p| p * 2 dentro de prices.iter().map(...)?

Puntuación: 0 / 3