Erro E0593 do Rust: closure is expected to take a single 2-tuple as argument
O E0593 quer dizer que você entregou uma closure (função anônima), ou uma função, para outra coisa, como map, sort_by ou thread::spawn, e o número de parâmetros dela não bate com o jeito como ela vai ser chamada. O caso mais comum, de longe: um iterador entrega uma tupla para a sua closure, e você escreveu dois parâmetros. Na maioria das vezes a correção é um par de parênteses, e o rustc costuma até escrevê-la para você. Se é você quem chama a closure com a quantidade errada de valores, o erro é outro, o E0057. Para os outros erros desta série, veja Erros do compilador Rust.
A mensagem de erro
Seção intitulada “A mensagem de erro”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)) // ❌ dois 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)) // ❌ dois parâmetros6 + .map(|(i, name)| format!("{}. {name}", i + 1)) // ❌ dois parâmetros |Em português: “esperava-se que a closure recebesse uma única tupla de 2 elementos como argumento, mas ela recebe 2 argumentos separados”. O help: mostra a correção: trocar |i, name| por |(i, name)|.
Este é o programa que gera o erro:
fn main() { let names = vec!["Ferris", "Corro", "Rusty"]; let lines: Vec<String> = names .iter() .enumerate() .map(|i, name| format!("{}. {name}", i + 1)) // ❌ dois parâmetros .collect(); println!("{:?}", lines);}O E0593 também aparece com outras primeiras linhas. Todas querem dizer “número errado 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 argumentNa ordem: a closure deveria receber 2 argumentos e recebe 1; deveria receber nenhum e recebe 1; a função deveria receber 1 e recebe 2; e o caso inverso do exemplo, dois argumentos separados esperados, mas a closure recebe uma tupla.
O que significa
Seção intitulada “O que significa”Olhe as duas marcas embaixo da linha 6:
^^^aponta paramap. É ele quem chama a sua closure, e é ele quem decide quantos valores passa.---------aponta para a sua lista de parâmetros|i, name|. É o que você prometeu receber.
Os dois não combinam. O enumerate() transforma cada item em uma tupla, (índice, item), e o map passa essa tupla para a sua closure como um argumento só. A sua closure pede dois argumentos separados.
Pense numa caixa de correio com uma fenda que aceita um envelope de cada vez. Você pode colocar duas cartas dentro do envelope (uma tupla), mas a fenda não aceita duas cartas soltas. |(i, name)| quer dizer “me dê o envelope, que eu abro aqui mesmo”.
A palavra “closure” ou “function” na mensagem diz o que você passou: uma closure escrita ali mesmo, ou uma fn com nome, como em .map(add).
Por que o Rust faz isso
Seção intitulada “Por que o Rust faz isso”Todo método que recebe uma closure diz exatamente como vai chamá-la. O map quer algo como FnMut(Self::Item) -> B: um argumento, o item. Com enumerate(), o item é (usize, &&str). É um valor só, que por acaso é uma tupla. O sort_by quer FnMut(&T, &T) -> Ordering: dois argumentos. O thread::spawn quer FnOnce() -> T: nenhum.
O Rust nunca separa uma tupla em vários argumentos por você, e nunca junta vários argumentos numa tupla. Isso deixa a regra simples: a closure recebe exatamente o que a assinatura do método diz. Depois que você aprende a ler essas assinaturas, consegue prever o que qualquer método de iterador vai entregar.
Você pode ler mais sobre as traits Fn por trás disso em Traits de closures, e sobre tuplas em Desestruturando uma tupla.
Como corrigir
Seção intitulada “Como corrigir”Correção 1: Desempacote a tupla na lista de parâmetros
Seção intitulada “Correção 1: Desempacote a tupla na lista de parâmetros”Escolha esta quando a mensagem disser “a single 2-tuple” (uma única tupla de 2 elementos). É a correção que você vai usar com mais frequência. Acrescente parênteses: |(i, name)| recebe uma tupla e dá nome às duas partes dela. Funciona com enumerate, zip e ao percorrer um map:
use std::collections::BTreeMap;
fn main() { let names = vec!["Ferris", "Corro", "Rusty"];
// enumerate entrega uma tupla (índice, nome) por item let lines: Vec<String> = names .iter() .enumerate() .map(|(i, name)| format!("{}. {name}", i + 1)) .collect(); println!("{:?}", lines);
// percorrer um map também entrega uma tupla (chave, valor) por item let mut stock = BTreeMap::new(); stock.insert("maçãs", 3); stock.insert("peras", 0); stock.insert("ameixas", 5); let total: i32 = stock.iter().map(|(_name, count)| *count).sum(); println!("{total} frutas no estoque");}Saída:
["1. Ferris", "2. Corro", "3. Rusty"]8 frutas no estoqueO _ no começo de _name diz ao Rust “eu sei que não estou usando isto”. Você também pode escrever só _. A mesma ideia com zip aparece na página do E0057.
Correção 2: Use o número de parâmetros com que o método chama
Seção intitulada “Correção 2: Use o número de parâmetros com que o método chama”Escolha esta quando a mensagem disser “expected to take N arguments” (deveria receber N argumentos). Veja o que o método passa e escreva exatamente essa quantidade de parâmetros:
- O
sort_bycompara dois itens:|a, b|. for_each,mapefilterpassam um item:|item|, ou|_|se você não precisa dele.- O
thread::spawnnão passa nada:||, normalmentemove ||.
use std::thread;
fn main() { // sort_by compara dois itens por vez: dois parâmetros let mut scores = vec![42, 7, 99, 15]; scores.sort_by(|a, b| b.cmp(a)); println!("{:?}", scores);
// for_each entrega um item: um parâmetro (use _ se não precisar dele) let tasks = vec!["escrever", "testar", "publicar"]; tasks.iter().for_each(|task| println!("a fazer: {task}")); tasks.iter().for_each(|_| print!("*")); println!();
// thread::spawn chama a closure sem nada: zero parâmetros let id = 1; let handle = thread::spawn(move || format!("trabalhador {id} terminou")); println!("{}", handle.join().unwrap());}Saída:
[99, 42, 15, 7]a fazer: escrevera fazer: testara fazer: publicar***trabalhador 1 terminouPara levar dados para dentro de uma thread, não acrescente um parâmetro. Capture o valor com move, como o id acima. Veja Concorrência e Ordenação.
Correção 3: Envolva a função com nome, ou faça ela receber uma tupla
Seção intitulada “Correção 3: Envolva a função com nome, ou faça ela receber uma tupla”Escolha esta quando a mensagem disser “function is expected to take…” (esperava-se que a função recebesse…). Você passou uma função pelo nome, como .map(add), e os parâmetros dela não encaixam. Aqui os itens são pares, mas add recebe dois 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 callO rustc diz que o map precisa de uma função que receba uma única tupla, mas add recebe dois argumentos separados. Você pode envolver a função numa closure pequena que desempacota a tupla e chama add, ou escrever uma versão que recebe a tupla diretamente:
fn add(a: i32, b: i32) -> i32 { a + b}
// uma versão que recebe o par como uma tupla sófn add_pair((a, b): (i32, i32)) -> i32 { a + b}
fn main() { let pairs = vec![(1, 2), (3, 4), (10, 20)];
// envolver: entra uma tupla, saem dois argumentos let sums: Vec<i32> = pairs.iter().map(|&(a, b)| add(a, b)).collect(); println!("{:?}", sums);
// ou passar uma função que já recebe uma tupla let sums2: Vec<i32> = pairs.into_iter().map(add_pair).collect(); println!("{:?}", sums2);}Saída:
[3, 7, 30][3, 7, 30]O & em |&(a, b)| está ali porque o .iter() empresta cada par como &(i32, i32), e o padrão copia os dois números para fora. A closure intermediária costuma ser a melhor escolha: add continua sendo uma função normal de dois argumentos, que o resto do código pode chamar.
Se a sua própria função genérica pede Fn(i32) -> i32 e você passa um add de dois argumentos, aparece “function is expected to take 1 argument, but it takes 2 arguments”. A correção é uma closure intermediária que preenche o valor que falta, mostrada na Correção 3 do E0057.
Onde você costuma ver esse erro
Seção intitulada “Onde você costuma ver esse erro”HashMap::retain (ao contrário). A maioria dos métodos de map entrega uma tupla, mas o retain passa a chave e o valor como dois argumentos separados. Então stock.retain(|(name, count)| *count > 0) dá “closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument”. Correção: tire os parênteses, stock.retain(|_name, count| *count > 0). O Vec::retain recebe um item, como de costume.
Esquecer o parâmetro. tasks.iter().for_each(|| println!("tarefa")) dá “closure is expected to take 1 argument, but it takes 0 arguments”, e o rustc sugere |_|. Correção: |_|, ou |task| se você quiser usar o item.
Threads. thread::spawn(|id| ...) dá “closure is expected to take 0 arguments, but it takes 1 argument”. Correção: thread::spawn(move || ...), capturando id de fora.
Um parâmetro do tipo ponteiro de função. Se a sua função recebe f: fn(i32) -> i32 (um ponteiro de função simples, não um F: Fn genérico), passar um add de dois argumentos dá o E0308, com o rótulo “incorrect number of function parameters” (número incorreto de parâmetros da função). A correção é a mesma: uma closure intermediária como |x| add(x, 10). Closures que não capturam nada podem ser passadas como ponteiros fn.
Erros relacionados
Seção intitulada “Erros relacionados”- E0057: closure takes N arguments but M were supplied: você mesmo chama uma closure com a quantidade errada de valores.
- E0631: type mismatch in closure arguments: a quantidade está certa, mas um parâmetro tem o tipo errado, muitas vezes
i32onde o iterador entrega&i32. - E0061: function takes N arguments but M were supplied: chamar uma função normal com o número errado de argumentos.
- E0308: mismatched types: o que você recebe com um parâmetro do tipo ponteiro
fn(...), ou com|(a, b)|quando os itens nem são tuplas. - E0599: no method found: às vezes aparece logo depois do E0593, por exemplo “the method
collectexists … but its trait bounds were not satisfied”. Corrija o E0593 primeiro e ele some. - Todos os erros do compilador: a lista completa de páginas de erros.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. names.iter().enumerate().map(|i, name| ...) dá o E0593. Qual é a correção?
2. Quantos parâmetros a closure de scores.sort_by(...) precisa ter?
3. thread::spawn(|id| println!("{id}")) dá o E0593. Como levar o id para dentro da thread?
Pontuação: 0 / 3
Para saber mais
Seção intitulada “Para saber mais”- E0593 — Rust error codes index (em inglês): a explicação oficial deste erro.
- Closures — The Rust Book (em inglês): a sintaxe das closures, como os tipos dos parâmetros são inferidos e as traits
Fn. - As input parameters — Rust by Example (em inglês): como o bound
Fnde uma função decide que closure ela aceita. Iterator::enumerate— documentação da biblioteca padrão (em inglês): mostra que cada item vira uma tupla(índice, valor).HashMap::retain— documentação da biblioteca padrão (em inglês): o método de map cuja closure recebe a chave e o valor como dois argumentos.