Pular para o conteúdo

Erro E0282 do Rust: type annotations needed (falta anotação de tipo)

O E0282 quer dizer que o Rust tentou descobrir um tipo sozinho e não encontrou pistas suficientes. Não há nada errado com a sua lógica. O compilador só precisa que você diga o nome de um tipo, normalmente com alguns caracteres a mais. Esta página mostra a correção primeiro e depois explica como funciona a dedução de tipos do Rust (a inferência de tipos, ou type inference), para você saber quando dar uma ajuda. Para os outros erros desta série, veja Erros do compilador Rust.

error[E0282]: type annotations needed for `Vec<_>`
--> src/main.rs:2:9
|
2 | let mut scores = Vec::new(); // um Vec de... quê?
| ^^^^^^^^^^ ---------- type must be known at this point
|
help: consider giving `scores` an explicit type, where the type for type parameter `T` is specified
|
2 | let mut scores: Vec<T> = Vec::new(); // um Vec de... quê?
| ++++++++

Em português: “são necessárias anotações de tipo para Vec<_>”. O compilador diz que precisa saber o tipo naquele ponto e sugere dar a scores um tipo explícito, informando o tipo do parâmetro T.

Este é o programa que gera o erro:

fn main() {
let mut scores = Vec::new(); // um Vec de... quê?
println!("Scores recorded: {}", scores.len());
}

As palavras “type annotations needed” também aparecem com outros três códigos. Eles querem dizer a mesma coisa, e as correções desta página servem para todos:

  • E0283: .collect() ou .sum() sem um tipo de destino. O rustc acrescenta uma nota como the type must implement `FromIterator<&str>` ou `Sum<&i32>` (o tipo precisa implementar…), porque muitos tipos poderiam ser o resultado.
  • E0284: "42".parse().unwrap() sem um tipo de destino. A nota diz cannot satisfy `<_ as FromStr>::Err == _`, que é um jeito assustador de dizer “converter para quê?”.
  • E0790: let settings = Default::default();. A primeira linha diz cannot call associated function on trait without specifying the corresponding `impl` type (não dá para chamar a função associada da trait sem dizer o tipo do impl): o Rust não sabe de quem é o valor padrão que você quer.

O código que você recebe depende da linha exata, e pode mudar se você acrescentar mais código. Por exemplo, let age = "42".parse().unwrap(); dá E0284, mas se a linha seguinte chamar age.pow(2), vira E0282. Não se preocupe com o número; procure a variável que o rustc aponta.

Olhe as duas marcas embaixo da linha 2:

  1. ^^^^^^^^^^ embaixo de mut scores: esta é a variável cujo tipo o Rust não conseguiu descobrir.
  2. ---------- embaixo de Vec::new(): “type must be known at this point” (o tipo precisa ser conhecido neste ponto). Vec::new() cria uma lista vazia, mas uma lista vazia de quê? Números? Nomes? O Rust precisa saber para reservar o tipo certo de espaço.
  3. A ajuda sugere let mut scores: Vec<T>. Aqui T é só um marcador. Você o troca por um tipo de verdade, como Vec<u32>.

Pense numa caixa nos Correios com o campo “conteúdo” em branco. O atendente não pode enviá-la até alguém escrever alguma coisa ali. Você pode escrever, ou colocar algo dentro da caixa para o atendente ver.

Todo valor em Rust tem um tipo exato, decidido em tempo de compilação. Na maioria das vezes você não escreve esse tipo, porque o Rust lê o seu código e descobre sozinho. let count = 5; é um i32. let name = String::from("Ferris"); é uma String. Isso é inferência de tipos, e a lição Variáveis mostra mais exemplos.

A inferência usa pistas da função inteira, não de uma linha só. Se você chamar scores.push(90) mais adiante, isso é uma pista: o Vec guarda números. Se passar scores para uma função que recebe &[u32], a pista é ainda melhor. O E0282 aparece quando não há pista nenhuma, ou quando o Rust precisa do tipo antes de chegar à pista (por exemplo, para procurar um método).

Algumas funções são genéricas no tipo de retorno: parse, collect, sum, Default::default, Vec::new, None. Elas podem produzir muitos tipos diferentes, então o Rust não consegue escolher um por você. E ele não chuta, porque um chute errado mudaria o comportamento do programa sem você perceber. Veja Genéricos para entender como uma função pode retornar muitos tipos.

Escolha esta na maioria dos casos. Coloque : Tipo depois do nome da variável. É curto e ainda documenta o código para a próxima pessoa que for ler:

fn main() {
let scores: Vec<u32> = Vec::new();
let age: u32 = "42".parse().unwrap();
let words: Vec<&str> = "vermelho verde azul".split(' ').collect();
let winner: Option<String> = None;
println!("Pontuações registradas: {}", scores.len());
println!("Ano que vem você vai ter {}", age + 1);
println!("{:?}", words);
println!("{:?}", winner);
}

Saída:

Pontuações registradas: 0
Ano que vem você vai ter 43
["vermelho", "verde", "azul"]
None
▶ Experimente você mesmo

Você nem sempre precisa escrever o tipo completo. let words: Vec<_> = ...collect(); quer dizer “um Vec, e você descobre o que vai dentro”. O _ pede ao Rust para completar essa parte. Mais sobre como escrever tipos em Tipos de dados.

Escolha esta quando não há variável para anotar, por exemplo no meio de uma cadeia de chamadas de iteradores. Você coloca o tipo direto na chamada da função, com ::<Tipo>. O pessoal chama o ::<> de “turbofish” (peixe-turbo) porque ele lembra um pouco um peixe:

fn main() {
let input = "4 15 7";
let numbers = input
.split(' ')
.map(|s| s.parse::<u32>().unwrap())
.collect::<Vec<_>>();
let total = numbers.iter().sum::<u32>();
println!("{:?}", numbers);
println!("Total: {total}");
}

Saída:

[4, 15, 7]
Total: 26
▶ Experimente você mesmo

As três chamadas com turbofish mais comuns são parse::<i32>(), collect::<Vec<_>>() e sum::<i32>(). A Correção 1 e a Correção 2 são igualmente certas; let total: u32 = numbers.iter().sum(); faz o mesmo trabalho. Escolha a que ficar mais fácil de ler.

Correção 3: Use o valor, para o Rust inferir o tipo

Seção intitulada “Correção 3: Use o valor, para o Rust inferir o tipo”

Escolha esta quando o erro aparece porque o código ainda não está pronto. Muitas vezes você cria um Vec vazio, e o código que o preenche vem depois. Assim que você usa o valor, o Rust tem a pista dele e o erro some, sem precisar de anotação:

fn print_team(names: &[String]) {
for name in names {
println!("- {name}");
}
}
fn main() {
let mut team = Vec::new(); // tipo desconhecido... por enquanto
team.push(String::from("Ferris")); // agora o Rust sabe: Vec<String>
team.push(String::from("Corro"));
print_team(&team);
}

Saída:

- Ferris
- Corro
▶ Experimente você mesmo

A ordem importa um pouco. A pista precisa vir antes de o Rust precisar do tipo para chamar um método. Uma closure como let shout = |s| s.to_uppercase(); continua falhando, mesmo que você chame shout("hi") na linha seguinte, porque o Rust precisa saber o que é s para encontrar o .to_uppercase(). Nesse caso, escreva o tipo: |s: &str| s.to_uppercase(). Veja Closures.

Correção 4: Diga o tipo de None e de Default::default()

Seção intitulada “Correção 4: Diga o tipo de None e de Default::default()”

Escolha esta para valores que não trazem pista nenhuma. None não diz o que guardaria, e Default::default() não diz de quem é o valor padrão que você quer. Escreva o tipo dentro da expressão:

#[derive(Debug, Default)]
struct Settings {
volume: u8,
muted: bool,
}
fn main() {
let fastest_lap = None::<f64>; // em vez de: None
let settings = Settings::default(); // em vez de: Default::default()
let backup: Settings = Default::default(); // uma anotação também funciona
println!("{:?}", fastest_lap);
println!("{:?}", settings);
println!("Volume do backup: {}", backup.volume);
}

Saída:

None
Settings { volume: 0, muted: false }
Volume do backup: 0
▶ Experimente você mesmo

Settings::default() é a escolha mais clara: dá para ler como “o Settings padrão”. None::<f64> é prático, mas let fastest_lap: Option<f64> = None; (Correção 1) é mais comum. Veja Option.

Convertendo texto em número. let guess = line.trim().parse().unwrap(); dá E0284 (ou E0282, se você chamar um método em guess logo depois). parse pode produzir um i32, um u8, um f64 e muitos outros. Correção: let guess: i32 = ... ou .parse::<i32>(). Comparar guess com uma variável i32 mais adiante não basta aqui, porque o parse precisa do tipo na própria chamada.

.collect(). Ele pode montar um Vec, uma String, um HashSet, um HashMap… então sempre precisa saber o destino. Correção: let words: Vec<_> = ...collect(); ou .collect::<Vec<_>>(). Veja Iteradores.

.sum() e .product(). nums.iter().sum() dá E0283 com the type must implement `Sum<&i32>`. Somar i32 poderia dar um i32, mas outros tipos numéricos também podem ser somas. Correção: let total: i32 = nums.iter().sum(); ou .sum::<i32>().

Uma coleção vazia no construtor de uma struct. let cache = HashMap::new(); numa função new() que você ainda não terminou de escrever. Correção: escreva o tipo completo uma vez (HashMap<String, u32>), ou retorne o valor numa struct cujo campo já tem o tipo declarado, que é uma pista que o Rust consegue usar.

  • E0308: tipos incompatíveis. Acontece quando a sua anotação e o valor discordam, como em let age: u32 = "42";.
  • E0277: restrição de trait não satisfeita. Você informou um tipo, mas ele não pode ser montado daquele jeito, como em let n: Vec<i32> = "1 2".split(' ').collect(); (isso é um Vec<&str>; converta cada item antes).
  • E0599: método não encontrado, por exemplo quando você anota let n: u32 e depois chama nele um método de String, como .trim().
  • E0004: padrões não exaustivos, outro erro em que o rustc escreve a correção para você.
  • Todos os erros do compilador: a lista completa de páginas de erro.

Teste o que aprendeu

1. Qual linha corrige o E0282 em let ids = Vec::new(); quando o Vec deve guardar valores u64?

2. Por que let n = "7".parse().unwrap(); precisa de um tipo, se o texto é claramente um número?

3. O que o _ quer dizer em .collect::<Vec<_>>()?

Pontuação: 0 / 3