Ir al contenido

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.

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ámetros
6 + .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 argument
error[E0593]: closure is expected to take 0 arguments, but it takes 1 argument
error[E0593]: function is expected to take 1 argument, but it takes 2 arguments
error[E0593]: closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument

De 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.

Mira las dos marcas bajo la línea 6:

  • ^^^ señala map. 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).

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.

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 fruta
▶ Pruébalo tú mismo

El _ 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_by compara dos elementos: |a, b|.
  • for_each, map y filter pasan un elemento: |item|, o |_| si no lo necesitas.
  • thread::spawn no pasa nada: ||, normalmente move ||.
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: escribir
pendiente: probar
pendiente: publicar
***
trabajador 1 terminado
▶ Pruébalo tú mismo

Para 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 call

La 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 tupla
fn 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]
▶ Pruébalo tú mismo

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.

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.

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