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.
A mensagem de erro
Seção intitulada “A mensagem de erro”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 othersnote: 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…).
O que significa
Seção intitulada “O que significa”Leia a mensagem de cima para baixo:
^^^^^embaixo detotal: esta é a variável cujo tipo o Rust não conseguiu decidir.---embaixo desum: “type must be known at this point” (o tipo precisa ser conhecido neste ponto). Osumpode retornar muitos tipos diferentes, então o Rust precisa da resposta aqui.- A nota:
the type must implement `Sum<&i32>`. Esta é a regra que o Rust encontrou: “totalprecisa ser de um tipo que pode ser construído somando valores&i32”. - A lista de ajuda: os tipos que sabem ser “uma soma de alguma coisa”. São muitos: todos os tipos numéricos,
Duration, atéOptioneResult. 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 umimplque 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.
Por que o Rust faz isso
Seção intitulada “Por que o Rust faz isso”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.
E0282, E0283, E0284: um problema, três códigos
Seção intitulada “E0282, E0283, E0284: um problema, três códigos”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.
Como corrigir
Seção intitulada “Como corrigir”Correção 1: Anote a variável
Seção intitulada “Correção 1: Anote a variável”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: 3O 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<_> = ....
Correção 2: Use o turbofish no sum e no collect
Seção intitulada “Correção 2: Use o turbofish no sum e no collect”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: tcsAs 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 argumentsO 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 Paulo200 200 42300 cabe num u8? falseString::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 centavosNem 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.
Onde você costuma ver esse erro
Seção intitulada “Onde você costuma ver esse erro”.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();.
Erros relacionados
Seção intitulada “Erros relacionados”- 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
Seção intitulada “Teste o que aprendeu”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
Para saber mais
Seção intitulada “Para saber mais”- E0283 — Rust error codes index (em inglês): a explicação oficial, com o exemplo clássico: os
chars()invertidos devem virar umVec<char>ou umaString? - Processing a Series of Items with Iterators — The Rust Book (em inglês): como
sumecollectconsomem um iterador, com exemplos anotados. Iterator::collect— documentação da biblioteca padrão (em inglês): mostra o estilo com anotação e o estilo com turbofish lado a lado.Sum— documentação da biblioteca padrão (em inglês): a trait por trás do.sum()e a lista completa de tipos que a implementam.Into— documentação da biblioteca padrão (em inglês): por que você implementaFrome chama.into(), e como o tipo de destino é escolhido.