Error E0593 de Rust: closure is expected to take a single 2-tuple as argument
E0593 significa que le pasaste una closure (o una función) a otra cosa, como map, sort_by o thread::spawn, y su número de parámetros no coincide con la forma en que la van a llamar. El caso más común, con diferencia: un iterador le da a tu closure una sola tupla, y tú escribiste dos parámetros. Casi siempre se arregla con dos paréntesis, y normalmente rustc te escribe la solución. Si eres tú quien llama a la closure con un número equivocado de valores, eso es E0057. 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[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments --> src/main.rs:6:10 |6 | .map(|i, name| format!("{}. {name}", i + 1)) // ❌ dos parámetros | ^^^ --------- takes 2 distinct arguments | | | expected closure that takes a single 2-tuple as argument |help: change the closure to accept a tuple instead of individual arguments |6 - .map(|i, name| format!("{}. {name}", i + 1)) // ❌ dos parámetros6 + .map(|(i, name)| format!("{}. {name}", i + 1)) // ❌ dos parámetros |En español: “se esperaba que la closure recibiera una sola tupla de 2 elementos como argumento, pero recibe 2 argumentos distintos”. El help te pide que cambies la closure para que acepte una tupla en lugar de argumentos sueltos, y te muestra la línea ya corregida.
Este es el programa que lo produce:
fn main() { let names = vec!["Ferris", "Corro", "Rusty"]; let lines: Vec<String> = names .iter() .enumerate() .map(|i, name| format!("{}. {name}", i + 1)) // ❌ dos parámetros .collect(); println!("{:?}", lines);}E0593 también puede empezar con otras primeras líneas. Todas significan “número equivocado de parámetros”:
error[E0593]: closure is expected to take 2 arguments, but it takes 1 argumenterror[E0593]: closure is expected to take 0 arguments, but it takes 1 argumenterror[E0593]: function is expected to take 1 argument, but it takes 2 argumentserror[E0593]: closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argumentDe arriba abajo: se esperaban 2 argumentos y recibe 1; se esperaban 0 y recibe 1; una función que debía recibir 1 y recibe 2; y el caso al revés, se esperaban 2 argumentos sueltos y recibe una sola tupla.
Qué significa
Sección titulada «Qué significa»Mira las dos marcas bajo la línea 6:
^^^señalamap. Es quien llama a tu closure, y decide cuántos valores le pasa.---------señala tu lista de parámetros|i, name|. Es lo que tú prometiste aceptar.
No coinciden. enumerate() convierte cada elemento en una tupla, (índice, elemento), y map le pasa esa tupla a tu closure como un solo argumento. Tu closure pide dos argumentos separados.
Piensa en una ranura de buzón que acepta un sobre cada vez. Puedes meter dos cartas dentro del sobre (una tupla), pero la ranura no acepta dos cartas sueltas. |(i, name)| dice “dame el sobre, que lo abro aquí mismo”.
La palabra “closure” o “function” en el mensaje te dice qué pasaste: una closure escrita en el sitio, o una fn con nombre, como .map(add).
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»Cada método que recibe una closure dice exactamente cómo la va a llamar. map quiere algo como FnMut(Self::Item) -> B: un argumento, el elemento. Con enumerate() el elemento es (usize, &&str). Es un solo valor que resulta ser una tupla. sort_by quiere FnMut(&T, &T) -> Ordering: dos argumentos. thread::spawn quiere FnOnce() -> T: ninguno.
Rust nunca separa una tupla en argumentos sueltos por ti, ni junta argumentos sueltos en una tupla. Así la regla es sencilla: lo que dice la firma del método es lo que recibe tu closure. Cuando aprendes a leer esas firmas, puedes predecir qué te entrega cualquier método de un iterador.
Puedes leer más sobre los traits Fn que hay detrás en Traits de closures, y sobre las tuplas en Desestructurar una tupla.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: Desempaquetar la tupla en la lista de parámetros
Sección titulada «Solución 1: Desempaquetar la tupla en la lista de parámetros»Elige esta cuando el mensaje dice “a single 2-tuple” (una sola tupla de 2 elementos). Es la solución que necesitarás más a menudo. Añade paréntesis: |(i, name)| recibe una tupla y les pone nombre a sus dos partes. Funciona con enumerate, con zip y al recorrer un mapa:
use std::collections::BTreeMap;
fn main() { let names = vec!["Ferris", "Corro", "Rusty"];
// enumerate da una tupla (índice, nombre) por elemento let lines: Vec<String> = names .iter() .enumerate() .map(|(i, name)| format!("{}. {name}", i + 1)) .collect(); println!("{:?}", lines);
// recorrer un mapa también da una tupla (clave, valor) por elemento let mut stock = BTreeMap::new(); stock.insert("manzanas", 3); stock.insert("peras", 0); stock.insert("ciruelas", 5); let total: i32 = stock.iter().map(|(_name, count)| *count).sum(); println!("{total} piezas de fruta");}Salida:
["1. Ferris", "2. Corro", "3. Rusty"]8 piezas de frutaEl _ al principio de _name le dice a Rust “ya sé que no lo uso”. También puedes escribir solo _. La misma idea con zip aparece en la página de E0057.
Solución 2: Dale al método el número de parámetros con el que llama
Sección titulada «Solución 2: Dale al método el número de parámetros con el que llama»Elige esta cuando el mensaje dice “expected to take N arguments” (se esperaba que recibiera N argumentos). Mira qué pasa el método y escribe exactamente esa cantidad de parámetros:
sort_bycompara dos elementos:|a, b|.for_each,mapyfilterpasan un elemento:|item|, o|_|si no lo necesitas.thread::spawnno pasa nada:||, normalmentemove ||.
use std::thread;
fn main() { // sort_by compara dos elementos a la vez: dos parámetros let mut scores = vec![42, 7, 99, 15]; scores.sort_by(|a, b| b.cmp(a)); println!("{:?}", scores);
// for_each entrega un elemento: un parámetro (usa _ si no lo necesitas) let tasks = vec!["escribir", "probar", "publicar"]; tasks.iter().for_each(|task| println!("pendiente: {task}")); tasks.iter().for_each(|_| print!("*")); println!();
// thread::spawn llama a la closure sin nada: cero parámetros let id = 1; let handle = thread::spawn(move || format!("trabajador {id} terminado")); println!("{}", handle.join().unwrap());}Salida:
[99, 42, 15, 7]pendiente: escribirpendiente: probarpendiente: publicar***trabajador 1 terminadoPara pasarle datos a un hilo, no añadas un parámetro. Captúralos con move, como id arriba. Mira Concurrencia y Ordenar y buscar.
Solución 3: Envolver una función con nombre, o hacer que reciba una tupla
Sección titulada «Solución 3: Envolver una función con nombre, o hacer que reciba una tupla»Elige esta cuando el mensaje dice “function is expected to take…” (se esperaba que la función recibiera…). Pasaste una función por su nombre, como .map(add), y sus parámetros no encajan. Aquí los elementos son pares, pero add recibe dos números:
error[E0593]: function is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments --> src/main.rs:7:48 |1 | fn add(a: i32, b: i32) -> i32 { | ----------------------------- takes 2 distinct arguments...7 | let sums: Vec<i32> = pairs.into_iter().map(add).collect(); | --- ^^^ expected function that takes a single 2-tuple as argument | | | required by a bound introduced by this callLa etiqueta required by a bound introduced by this call quiere decir que es la llamada a map la que exige esa forma. Puedes envolver la función en una closure pequeña que desempaquete la tupla y la llame, o escribir una versión que reciba la tupla directamente:
fn add(a: i32, b: i32) -> i32 { a + b}
// una versión que recibe el par como una sola tuplafn add_pair((a, b): (i32, i32)) -> i32 { a + b}
fn main() { let pairs = vec![(1, 2), (3, 4), (10, 20)];
// envolver: entra una tupla, salen dos argumentos let sums: Vec<i32> = pairs.iter().map(|&(a, b)| add(a, b)).collect(); println!("{:?}", sums);
// o pasar una función que ya recibe una tupla let sums2: Vec<i32> = pairs.into_iter().map(add_pair).collect(); println!("{:?}", sums2);}Salida:
[3, 7, 30][3, 7, 30]El & de |&(a, b)| está ahí porque .iter() presta cada par como &(i32, i32), y el patrón copia los dos números. El envoltorio suele ser la mejor opción: add sigue siendo una función normal de dos argumentos que el resto de tu código puede llamar.
Si tu propia función genérica pide Fn(i32) -> i32 y le pasas una add de dos argumentos, obtienes “function is expected to take 1 argument, but it takes 2 arguments” (se esperaba que la función recibiera 1 argumento, pero recibe 2). La solución es un envoltorio que rellene el valor que falta, como en la Solución 3 de E0057.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»HashMap::retain (al revés). Casi todos los métodos de los mapas te dan una tupla, pero retain pasa la clave y el valor como dos argumentos separados. Así que stock.retain(|(name, count)| *count > 0) da “closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument” (se esperaban 2 argumentos distintos, pero recibe una sola tupla). Solución: quita los paréntesis, stock.retain(|_name, count| *count > 0). Vec::retain recibe un elemento, como siempre.
Olvidar el parámetro. tasks.iter().for_each(|| println!("task")) da “closure is expected to take 1 argument, but it takes 0 arguments” (se esperaba 1 argumento, pero recibe 0), y rustc sugiere |_|. Solución: |_|, o |task| si quieres usarlo.
Hilos. thread::spawn(|id| ...) da “closure is expected to take 0 arguments, but it takes 1 argument” (se esperaban 0 argumentos, pero recibe 1). Solución: thread::spawn(move || ...) y captura id desde fuera.
Un parámetro que es un puntero a función. Si tu función recibe f: fn(i32) -> i32 (un puntero a función normal, no un genérico F: Fn), pasarle una add de dos argumentos da E0308, con la etiqueta “incorrect number of function parameters” (número incorrecto de parámetros de la función). La solución es la misma: una closure envoltorio como |x| add(x, 10). Las closures que no capturan nada se pueden pasar como punteros fn.
Errores relacionados
Sección titulada «Errores relacionados»- E0057: la closure recibe N argumentos, pero se pasaron M: llamas tú mismo a una closure con un número equivocado de valores.
- E0631: type mismatch in closure arguments: el número es correcto, pero un parámetro tiene el tipo equivocado, a menudo
i32donde el iterador da&i32. - E0061: la función recibe N argumentos, pero se pasaron M: llamar a una función normal con un número equivocado de argumentos.
- E0308: mismatched types: lo que obtienes con un parámetro de tipo
fn(...), o con|(a, b)|cuando los elementos ni siquiera son tuplas. - E0599: no method found: a veces aparece justo después de E0593, por ejemplo “the method
collectexists … but its trait bounds were not satisfied”. Arregla primero E0593 y desaparece. - 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. names.iter().enumerate().map(|i, name| ...) da E0593. ¿Cuál es la solución?
2. ¿Cuántos parámetros necesita la closure de scores.sort_by(...)?
3. thread::spawn(|id| println!("{id}")) da E0593. ¿Cómo metes id en el hilo?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- E0593 — Rust error codes index (en inglés): la explicación oficial de este error.
- Closures — El libro de Rust (en inglés): la sintaxis de las closures, cómo se deducen los tipos de sus parámetros y los traits
Fn. También existe una traducción al español de la comunidad del libro. - As input parameters — Rust by Example (en inglés): cómo la restricción
Fnde una función decide qué closure acepta. Iterator::enumerate— documentación de la biblioteca estándar (en inglés): muestra que cada elemento se convierte en una tupla(índice, valor).HashMap::retain— documentación de la biblioteca estándar (en inglés): el método de los mapas cuya closure recibe la clave y el valor como dos argumentos.