Pular para o conteúdo

Erro E0283 do Rust: type annotations needed (sum, collect e into)

O E0283 quer dizer que o Rust sabe o que o seu valor precisa ser capaz de fazer, mas muitos tipos conseguem fazer isso, e o Rust não escolhe um por você. Você costuma encontrá-lo logo depois de .sum(), .collect() ou .into(). É um dos erros mais tranquilos de corrigir: você diz o nome do tipo que quer, e pronto. Para os outros erros desta série, veja Erros do compilador Rust.

error[E0283]: type annotations needed
--> src/main.rs:3:9
|
3 | let total = prices.iter().sum();
| ^^^^^ --- type must be known at this point
|
= note: the type must implement `Sum<&i32>`
= help: the following types implement trait `Sum<A>`:
`Duration` implements `Sum<&'a Duration>`
`Duration` implements `Sum`
`Option<T>` implements `Sum<Option<U>>`
`Result<T, E>` implements `Sum<Result<U, E>>`
`Saturating<u128>` implements `Sum<&'a Saturating<u128>>`
`Saturating<u128>` implements `Sum`
`Saturating<u16>` implements `Sum<&'a Saturating<u16>>`
`Saturating<u16>` implements `Sum`
and 88 others
note: required by a bound in `std::iter::Iterator::sum`
help: consider giving `total` an explicit type
|
3 | let total: /* Type */ = prices.iter().sum();
| ++++++++++++

Em português: “anotações de tipo necessárias”. O rustc diz que o tipo de total precisa ser conhecido ali, lista alguns dos tipos que poderiam ser o resultado e sugere escrever o tipo de total de forma explícita.

Este é o programa que gera o erro:

fn main() {
let prices = vec![250, 120, 99]; // preços em centavos
let total = prices.iter().sum();
println!("Total: {total} cents");
}

.collect() e .into() dão o mesmo erro, com outra nota. Para let words = text.split(' ').collect();, a nota é the type must implement `FromIterator<&str>`. Para let name = "Ferris".into();, é the type must implement `From<&str>` (o tipo precisa implementar…).

Leia a mensagem de cima para baixo:

  1. ^^^^^ embaixo de total: esta é a variável cujo tipo o Rust não conseguiu decidir.
  2. --- embaixo de sum: “type must be known at this point” (o tipo precisa ser conhecido neste ponto). O sum pode retornar muitos tipos diferentes, então o Rust precisa da resposta aqui.
  3. A nota: the type must implement `Sum<&i32>`. Esta é a regra que o Rust encontrou: “total precisa ser de um tipo que pode ser construído somando valores &i32”.
  4. A lista de ajuda: os tipos que sabem ser “uma soma de alguma coisa”. São muitos: todos os tipos numéricos, Duration, até Option e Result. O Rust mostra oito e diz “and 88 others” (e mais 88). É isso que se quer dizer com multiple impls satisfy this bound (vários impls satisfazem este bound): mais de um tipo tem um impl que segue a regra.

Imagine pedir numa lanchonete “alguma coisa para beber”. É uma regra de verdade (precisa ser bebível), mas suco, café e água servem. O garçom não pode trazer nada até você escolher. O E0283 é o Rust segurando a bandeja e esperando a sua resposta.

sum, collect e into são genéricos no tipo de retorno. É o tipo que você pede que decide o que a função faz. A trait (Sum, FromIterator, From) é só a regra. Então, quando nada diz ao Rust qual é o tipo, ele tem uma regra, mas não uma resposta única.

E a resposta importa. Coletar as mesmas palavras num Vec<&str> mantém as repetidas, mas coletá-las num HashSet<&str> remove as repetições. Somar num u8 estoura em 255, enquanto num u64 não. Se o Rust escolhesse um tipo por você, uma pequena mudança em outro lugar poderia alterar o comportamento do programa sem você perceber. O Rust prefere perguntar uma vez.

O jeito como o Rust descobre os tipos sozinho (a inferência de tipos, ou type inference) está explicado na página do E0282, então não vamos repetir aqui. Para as traits por trás desses métodos, veja Traits e Genéricos.

Os três dizem “type annotations needed”, e para você querem dizer a mesma coisa: escreva o tipo. O código só conta o que o Rust estava fazendo quando travou:

  • E0282: o Rust não tem pista nenhuma sobre o tipo, ou precisou do tipo antes de chegar à pista (por exemplo, para procurar um método como .len()).
  • E0283 (esta página): o Rust tem uma pista na forma de uma regra de trait (must implement `Sum<&i32>`), mas muitos tipos seguem essa regra.
  • E0284: a regra é sobre um tipo que pertence ao tipo desconhecido, como o tipo de erro do parse (cannot satisfy `<_ as FromStr>::Err == _`). O Rust não consegue conferir até saber o tipo principal.

O código pode até mudar enquanto você digita. let words = text.split(' ').collect(); seguido de println!("{:?}", words) é E0283. Mude a linha seguinte para words.len() e vira E0282, porque agora o Rust precisa do tipo para encontrar o método. As correções abaixo servem para os três.

Escolha esta na maioria das vezes. Acrescente : Tipo depois do nome da variável. É curto, e diz para quem ler o código exatamente o que você está construindo:

use std::collections::HashSet;
fn main() {
let prices = vec![250, 120, 99];
let total: u32 = prices.iter().sum();
let tags = "rust,web,rust,cli";
let list: Vec<&str> = tags.split(',').collect();
let unique: HashSet<&str> = tags.split(',').collect();
println!("Total: {total} centavos");
println!("{:?}", list);
println!("Tags únicas: {}", unique.len());
}

Saída:

Total: 469 centavos
["rust", "web", "rust", "cli"]
Tags únicas: 3
▶ Experimente você mesmo

O mesmo collect(), dois resultados diferentes: o tipo que você escreve é mesmo a instrução. Você pode deixar a parte de dentro para o Rust com _, como em let list: Vec<_> = ....

Escolha esta quando não há variável para anotar: dentro de um println!, no meio de uma cadeia de iteradores ou como argumento de uma função. Coloque o tipo na chamada com ::<Tipo> (o “turbofish”):

fn main() {
let prices = vec![250, 120, 99];
println!("Total: {} centavos", prices.iter().sum::<u32>());
let line = "tea,cake,soup";
let items = line.split(',').collect::<Vec<_>>();
let initials = line.split(',').map(|s| &s[..1]).collect::<String>();
println!("{:?}", items);
println!("Iniciais: {initials}");
}

Saída:

Total: 469 centavos
["tea", "cake", "soup"]
Iniciais: tcs
▶ Experimente você mesmo

As palavras ficaram em inglês porque &s[..1] corta o primeiro byte, e letras acentuadas ocupam 2 bytes. O .product::<u64>() funciona do mesmo jeito. A Correção 1 e a Correção 2 são igualmente corretas, então escolha a que fica mais legível.

Correção 3: No .into(), diga o tipo de destino (o turbofish não funciona)

Seção intitulada “Correção 3: No .into(), diga o tipo de destino (o turbofish não funciona)”

Escolha esta para .into() e .try_into(). Dá vontade de escrever "Ferris".into::<String>(), mas isso é outro erro, porque o parâmetro de tipo pertence à trait Into, não ao método:

error[E0107]: method takes 0 generic arguments but 1 generic argument was supplied
--> src/main.rs:2:25
|
2 | let name = "Ferris".into::<String>();
| ^^^^ expected 0 generic arguments

O rustc diz que o método recebe 0 argumentos genéricos, mas você passou 1. Em vez disso, anote a variável, ou chame From direto no tipo de destino, o que também fica bem legível:

fn main() {
let name: String = "Ferris".into();
let city = String::from("São Paulo");
let small: u8 = 200;
let big: u64 = small.into();
let also_big = u64::from(small);
let level: i64 = 42;
let byte: u8 = level.try_into().unwrap();
let too_big = u8::try_from(300_i64);
println!("{name} mora em {city}");
println!("{big} {also_big} {byte}");
println!("300 cabe num u8? {}", too_big.is_ok());
}

Saída:

Ferris mora em São Paulo
200 200 42
300 cabe num u8? false
▶ Experimente você mesmo

String::from(...) e u64::from(...) dizem o tipo de destino logo no começo da linha, por isso muita gente prefere usá-los no lugar de .into() quando não há outra pista. Veja Conversões.

Correção 4: Use o valor onde o tipo dele é fixo

Seção intitulada “Correção 4: Use o valor onde o tipo dele é fixo”

Escolha esta quando o valor vai mesmo para um lugar com tipo conhecido, como um parâmetro de função ou um campo de struct. O Rust lê a função inteira, então esse uso mais adiante já basta:

fn print_receipt(total: u32, items: Vec<&str>) {
println!("{} itens, {} centavos", items.len(), total);
}
fn main() {
let prices = vec![250, 120, 99];
let total = prices.iter().sum(); // u32, graças a print_receipt
let items = "tea,cake,soup".split(',').collect(); // Vec<&str>, pelo mesmo motivo
print_receipt(total, items);
}

Saída:

3 itens, 469 centavos
▶ Experimente você mesmo

Nem todo uso conta. if prices.iter().sum() > 400 continua sendo E0283, porque o > pode comparar muitos pares de tipos. Passar o valor para um parâmetro genérico como impl Display também não ajuda, já que muitos tipos são Display. O uso precisa apontar para um tipo exato.

.collect() direto no println!. println!("{:?}", line.split(',').collect()); dá o E0283, e o próprio rustc sugere a correção: .collect::<Vec<_>>().

.try_into().unwrap(). let small = big.try_into().unwrap(); dá o E0283 com the type must implement `TryFrom<i64>`. Correção: let small: u8 = big.try_into().unwrap(); ou u8::try_from(big).unwrap().

Default::default() passado para uma função genérica. Com fn show(value: impl Debug), a chamada show(Default::default()) é E0283: o Rust não sabe de quem é o valor padrão que você quer. Correção: show(Settings::default()) ou show(u32::default()). Sozinho numa linha, let s = Default::default(); dá o E0790; veja Default.

A sua própria função genérica. Se um parâmetro de tipo só aparece no tipo de retorno, como em fn make<T: Default>() -> T, então let v = make(); é E0283. Correção: make::<Settings>() ou let v: Settings = make();.

  • E0282: type annotations needed, quando o Rust não tem pista nenhuma sobre o tipo (ou precisa dele para chamar um método).
  • E0284: type annotations needed com cannot satisfy `<_ as FromStr>::Err == _`, quase sempre vindo do .parse().
  • E0790: Default::default() (ou outra função de trait) chamada sem dizer qual é o tipo.
  • E0277: você disse o tipo, mas ele não pode ser construído desse jeito, como em let n: Vec<i32> = "1 2".split(' ').collect();.
  • E0107: número errado de argumentos genéricos, que é o que .into::<String>() dá.
  • Todos os erros do compilador: a lista completa de páginas de erros.

Teste o que aprendeu

1. O que a nota do E0283 the type must implement Sum<&i32> diz?

2. Qual linha corrige let name = "Ferris".into();?

3. Por que coletar as mesmas palavras dá resultados diferentes num Vec<&str> e num HashSet<&str>?

Pontuação: 0 / 3