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.
El mensaje de error
Sección titulada «El mensaje de error»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.
Qué significa
Sección titulada «Qué significa»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):mapva 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 uni32por 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.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»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 unVecque es tuyo) regala cada elemento:T.filteryfindte prestan el elemento otra vez, así que encima de.iter()recibes&&T.sort_byte presta dos elementos:&Ty&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.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: Quitar la anotación de tipo
Sección titulada «Solución 1: Quitar la anotación de tipo»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]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]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&i32en uni32. Úsalo con tiposCopypequeños, como números,charybool. (.cloned()hace lo mismo con tipos comoString, clonándolos)..into_iter()sobre unVecentrega los propios elementos. Después elVecqueda 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"]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ásfn 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"]Más sobre &str frente a String en Strings.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»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|.
Errores relacionados
Sección titulada «Errores relacionados»- E0593: la closure recibe un número equivocado de argumentos: el error hermano, para la cantidad de parámetros (por ejemplo
|a, b|cuando se pasa una sola tupla). - E0057: closure llamada con un número equivocado de argumentos: llamas tú mismo a una closure y le pasas demasiados o muy pocos valores.
- E0308: mismatched types: el error general de “tipo equivocado”, por ejemplo cuando el valor de retorno de la closure tiene el tipo equivocado, o con un parámetro de tipo
fn(...). - E0599: no method found: el error de
collectosumcon “trait bounds were not satisfied” que aparece después de E0631. Desaparece junto con E0631. - E0507: cannot move out of a borrowed value: cuando intentas quedarte con el ownership (propiedad) de elementos que solo tomaste prestados con
.iter(). - Todos los errores del compilador: la lista completa de páginas de errores.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»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
Para leer más
Sección titulada «Para leer más»- E0631 — Rust error codes index (en inglés): la explicación oficial de este error.
- Processing a Series of Items with Iterators — El libro de Rust (en inglés):
iter,into_itereiter_mut, y qué entrega cada uno. También existe una traducción al español de la comunidad del libro. - Closures — El libro de Rust (en inglés): cómo se deducen los tipos de los parámetros de una closure, y cuándo conviene escribirlos tú.
Iterator::copied— documentación de la biblioteca estándar (en inglés): convierte un iterador de&Ten un iterador deT.slice::sort_by— documentación de la biblioteca estándar (en inglés): la firma que muestra que la closure de comparación recibe dos referencias.