Pular para o conteúdo

Erro E0631 do Rust: type mismatch in closure arguments (tipo errado na closure)

O E0631 quer dizer que a sua closure (função anônima) tem a quantidade certa de parâmetros, mas pelo menos um deles tem o tipo errado. Em nove de cada dez casos, a diferença é um único &: você escreveu |x: i32|, mas o iterador empresta um &i32. A correção mais rápida costuma ser apagar a anotação de tipo e deixar o Rust descobrir sozinho. Esta página mostra essa correção primeiro e depois explica de onde vem o &, para você conseguir prever na próxima vez. Para os outros erros desta série, veja Erros do compilador 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(); // ❌ os itens são &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(); // ❌ os itens são &i32
| +

Em português: “tipos incompatíveis nos argumentos da closure”. O rustc mostra a assinatura que o map esperava e a que encontrou, e o help: sugere fazer a closure pegar o argumento emprestado, acrescentando um &.

Este é o programa que gera o erro:

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

Logo depois, o rustc mostra um segundo erro, mais longo: the method `collect` exists for struct `Map<...>`, but its trait bounds were not satisfied (E0599). Esse é só um efeito colateral. Corrija o E0631 e ele desaparece.

As duas linhas note são o coração da mensagem. Leia como “o método queria isto, você escreveu aquilo”:

  • expected fn(&{integer}) -> _: o map vai chamar a sua closure com uma referência para um inteiro. {integer} quer dizer “algum tipo inteiro que o Rust ainda não definiu”, e _ quer dizer “qualquer tipo de retorno”.
  • found fn(i32) -> _: a sua closure diz que recebe um i32 por valor.

Coloque as duas lado a lado e a única diferença é o &. A linha help: mostra a correção de um caractere.

Pense num guarda-volumes. Você entrega a sua mala e recebe um tíquete. O .iter() funciona do mesmo jeito: ele não entrega os itens, entrega tíquetes para eles (referências). Uma closure que insiste em receber a própria mala não aceita um tíquete.

Todo método de iterador decide exatamente que tipo vai passar para a sua closure, e esse tipo depende de como você criou o iterador:

  • v.iter() empresta cada item: &T.
  • v.into_iter() (num Vec que é seu) entrega cada item de vez: T.
  • filter e find emprestam o item de novo, então em cima de .iter() você recebe &&T.
  • sort_by empresta dois itens: &T e &T.

Quando você não escreve os tipos dos parâmetros, o Rust os preenche a partir do método, então eles sempre encaixam. Quando você escreve um tipo, o Rust acredita em você e confere. Ele não copia o valor escondido nem transforma uma referência num valor próprio, porque isso mudaria o que o seu código faz (e, numa String, uma cópia custa memória e tempo). A lição Iteradores mostra as três formas de obter um iterador, e Borrowing explica as referências e o borrowing (empréstimo).

Escolha esta primeiro. Parâmetros de closure quase nunca precisam de tipo: o Rust sabe exatamente o que map, filter e sort_by passam. Apague o : i32 e o 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];
// sem anotações de tipo: o Rust usa o que o iterador realmente entrega
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);
}

Saída:

[240, 90, 600]
[34, 50]
[99, 42, 15, 7]
▶ Experimente você mesmo

p * 2 funciona com um &i32 porque os operadores numéricos do Rust também aceitam referências. No filter, age é um &&i32, então **age segue as duas referências até chegar ao número. Comparações como b.cmp(a) funcionam direto com referências.

Correção 2: Escreva o tipo que o método realmente passa

Seção intitulada “Correção 2: Escreva o tipo que o método realmente passa”

Escolha esta quando você quer deixar o tipo visível, por exemplo para facilitar a leitura de uma cadeia longa. Siga o help: do rustc e acrescente o &. Você também pode colocar o & no padrão: |&p| desmonta a referência e entrega uma cópia simples do número (isso funciona para tipos Copy, como inteiros):

fn main() {
let prices = vec![120, 45, 300];
let ages = vec![12, 34, 17, 50];
let mut scores = vec![42, 7, 99, 15];
// escreva os tipos reais: & para .iter(), && dentro do 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));
// ou copie o número para fora com um padrão &
let tax: Vec<i32> = prices.iter().map(|&p| p / 10).collect();
println!("{:?}", doubled);
println!("{:?}", adults);
println!("{:?}", scores);
println!("{:?}", tax);
}

Saída:

[240, 90, 600]
[34, 50]
[99, 42, 15, 7]
[12, 4, 30]
▶ Experimente você mesmo

Repare na diferença: |p: &i32| quer dizer “p é uma referência”, enquanto |&p| quer dizer “eu recebo uma referência, me dê o que está por trás dela”.

Escolha esta quando você quer mesmo os valores, não referências. Há dois jeitos de obtê-los:

  • .iter().copied() transforma cada &i32 em um i32. Use com tipos Copy pequenos, como números, char e bool. (.cloned() faz o mesmo para tipos como String, clonando.)
  • .into_iter() num Vec entrega os próprios itens. Depois disso o Vec foi consumido, então escolha esta quando não precisar mais dele.
fn main() {
let prices = vec![120, 45, 300];
// .copied() transforma itens &i32 em itens i32
let doubled: Vec<i32> = prices.iter().copied().map(|p: i32| p * 2).collect();
println!("{:?}", doubled);
// .into_iter() num Vec entrega os próprios itens (e consome o 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);
}

Saída:

[240, 90, 600]
["FERRIS", "CORRO"]
▶ Experimente você mesmo

Se você ainda precisa de names depois do laço, continue com .iter() e receba &String. Tentar mover itens para fora de um Vec que você só pegou emprestado é outro erro, o E0507.

Correção 4: Faça a função com nome receber o que o iterador entrega

Seção intitulada “Correção 4: Faça a função com nome receber o que o iterador entrega”

Escolha esta quando você passa uma função pelo nome, como .map(shout). Aí a mensagem diz “function” em vez 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) -> _`

Os itens são &str, mas shout quer &String. Um bom hábito: faça as funções que só leem texto receberem &str. Assim elas aceitam literais de string, &String e mais. Quando o iterador acrescenta um & extra (como faz o filter), envolva a chamada numa closure curta. Numa chamada de função normal, o Rust tira os & extras para você (isso se chama deref coercion), mas ele não consegue fazer isso quando você passa a função pelo nome:

// recebe &str: funciona com literais de string, &String e mais
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"];
// os itens são &str, então shout pode ser passada pelo nome
let loud: Vec<String> = words.iter().copied().map(shout).collect();
println!("{:?}", loud);
// filter entrega &&str: uma closure pequena deixa o Rust converter para você
let long: Vec<&str> = words.iter().copied().filter(|w| is_long(w)).collect();
println!("{:?}", long);
}

Saída:

["CRAB", "RUSTACEAN", "FERRIS"]
["rustacean", "ferris"]
▶ Experimente você mesmo

As palavras ficaram em inglês porque is_long conta bytes com .len(), e letras acentuadas ocupam 2 bytes. Mais sobre &str e String em Strings.

Ordenar com parâmetros tipados. scores.sort_by(|a: i32, b: i32| b.cmp(&a)) dá o E0631 com expected closure signature `for<'a, 'b> fn(&'a {integer}, &'b {integer}) -> _`. A parte for<'a, 'b> só quer dizer “para qualquer empréstimo”. Correção: |a, b| b.cmp(a). Veja Ordenação.

Entradas de map com tipos próprios. stock.iter().map(|(name, count): (String, i32)| ...) num HashMap<String, i32> dá expected closure signature `fn((&String, &_)) -> _`. O .iter() de um map empresta tanto a chave quanto o valor. Correção: tire a anotação, ou escreva (&String, &i32).

A sua própria função genérica. Se fn apply<F: Fn(i32) -> i32>(f: F, x: i32) recebe uma closure |x: f64| x / 2.0, o rustc mostra expected closure signature `fn(i32) -> _` e found closure signature `fn(f64) -> _`, apontando para o bound. Correção: faça a closure receber i32 (e converta dentro, x as f64 / 2.0), ou mude o bound se f64 era o que você queria. Veja Traits de closures.

for_each com String. words.iter().for_each(|w: String| ...) num Vec<&str> espera fn(&&str). Um &str não é uma String. Correção: |w|, ou |w: &&str|.

Teste o que aprendeu

1. let v = vec![1, 2, 3]; Que tipo a closure de v.iter().map(|x| ...) recebe?

2. A nota diz expected closure signature fn(&{integer}) -> _, found closure signature fn(i32) -> _. Qual é a correção mais rápida?

3. O que |&p| p * 2 faz dentro de prices.iter().map(...)?

Pontuação: 0 / 3