Error E0283 de Rust: type annotations needed (sum, collect e into)
E0283 significa que Rust sabe qué tiene que poder hacer tu valor, pero hay muchos tipos que pueden hacerlo, y Rust no va a elegir uno por ti. Normalmente te lo encontrarás justo después de .sum(), .collect() o .into(). Es uno de los errores más amables de arreglar: escribes el tipo que quieres y listo. Para los demás errores de esta serie, mira Errores del compilador de Rust.
El mensaje de error
Sección titulada «El mensaje de error»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(); | ++++++++++++“Type annotations needed” quiere decir “faltan anotaciones de tipo”. La nota dice que el tipo tiene que implementar Sum<&i32>, la ayuda enumera algunos tipos que lo hacen, y el último help te propone darle a total un tipo explícito.
Este es el programa que lo produce:
fn main() { let prices = vec![250, 120, 99]; // precios en centavos let total = prices.iter().sum(); println!("Total: {total} cents");}.collect() y .into() dan el mismo error con otra nota. Para let words = text.split(' ').collect(); la nota es the type must implement `FromIterator<&str>`. Para let name = "Ferris".into(); es the type must implement `From<&str>`.
Qué significa
Sección titulada «Qué significa»Lee el mensaje de arriba abajo:
^^^^^bajototal: esta es la variable cuyo tipo Rust no pudo decidir.---bajosum: “type must be known at this point” (el tipo tiene que conocerse en este punto).sumpuede devolver muchos tipos distintos, así que Rust necesita la respuesta aquí.- La nota:
the type must implement `Sum<&i32>`. Esta es la regla que encontró Rust: “totaltiene que ser un tipo que se pueda formar sumando valores&i32”. - La lista de ayuda: los tipos que saben ser “una suma de algo”. Hay muchísimos: todos los tipos numéricos,
Duration, inclusoOptionyResult. Rust enseña ocho y dice “and 88 others” (y 88 más). A eso se refiere la gente cuando habla de multiple impls satisfy this bound (varias implementaciones cumplen esta restricción): más de un tipo tiene unimplque sigue la regla.
Imagina que pides en una cafetería “algo de beber”. Es una regla de verdad (tiene que poder beberse), pero el té, el zumo y el agua la cumplen. El camarero no puede traerte nada hasta que elijas. E0283 es Rust con la bandeja en la mano, esperando tu respuesta.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»sum, collect e into son genéricos en su tipo de retorno. El tipo que tú pides decide lo que hace la función. El trait (Sum, FromIterator, From) es solo la regla. Así que cuando nada le dice el tipo a Rust, tiene una regla pero no una única respuesta.
Y la respuesta importa. Recoger las mismas palabras en un Vec<&str> conserva los repetidos, pero recogerlas en un HashSet<&str> los elimina. Sumar en un u8 se desborda al pasar de 255, y en un u64 no. Si Rust eligiera un tipo por ti, un cambio diminuto en otra parte podría cambiar sin avisar cómo se comporta tu programa. Rust prefiere preguntar una vez.
Cómo deduce Rust los tipos por su cuenta (la inferencia de tipos) se explica en la página de E0282, así que no lo repetimos aquí. Para los traits que hay detrás de estos métodos, mira Traits y Genéricos.
E0282, E0283, E0284: un problema, tres códigos
Sección titulada «E0282, E0283, E0284: un problema, tres códigos»Los tres dicen “type annotations needed”, y para ti significan lo mismo: escribe el tipo. El código solo te dice qué estaba haciendo Rust cuando se atascó:
- E0282: Rust no tiene ninguna pista sobre el tipo, o necesitaba el tipo antes de llegar a la pista (por ejemplo, para buscar un método como
.len()). - E0283 (esta página): Rust tiene una pista en forma de regla de un trait (
must implement `Sum<&i32>`), pero muchos tipos cumplen esa regla. - E0284: la regla trata de un tipo que pertenece al tipo desconocido, como el tipo de error de
parse(cannot satisfy `<_ as FromStr>::Err == _`). Rust no puede comprobarla hasta que conozca el tipo principal.
El código puede incluso cambiar mientras escribes. let words = text.split(' ').collect(); seguido de println!("{:?}", words) es E0283. Cambia la línea siguiente por words.len() y pasa a ser E0282, porque ahora Rust necesita el tipo para encontrar el método. Las soluciones de abajo sirven para los tres.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»Solución 1: Anotar la variable
Sección titulada «Solución 1: Anotar la variable»Elige esta casi siempre. Añade : Tipo después del nombre de la variable. Es corto, y le dice a quien lea el código después exactamente qué estás construyendo:
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!("Etiquetas únicas: {}", unique.len());}Salida:
Total: 469 centavos["rust", "web", "rust", "cli"]Etiquetas únicas: 3El mismo collect(), dos resultados distintos: el tipo que escribes de verdad es la instrucción. Puedes dejarle el interior a Rust con _, como en let list: Vec<_> = ....
Solución 2: Usar el turbofish en sum y collect
Sección titulada «Solución 2: Usar el turbofish en sum y collect»Elige esta cuando no hay ninguna variable que anotar: dentro de un println!, en mitad de una cadena de iteradores o como argumento de una función. Pon el tipo en la llamada con ::<Tipo> (el “turbofish”). Las palabras del ejemplo se quedan en inglés porque &s[..1] corta el primer byte de cada una:
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!("Iniciales: {initials}");}Salida:
Total: 469 centavos["tea", "cake", "soup"]Iniciales: tcs.product::<u64>() funciona igual. La Solución 1 y la Solución 2 son igual de correctas, así que elige la que se lea mejor.
Solución 3: Con .into(), nombra el tipo de destino (el turbofish no funciona)
Sección titulada «Solución 3: Con .into(), nombra el tipo de destino (el turbofish no funciona)»Elige esta para .into() y .try_into(). Es tentador escribir "Ferris".into::<String>(), pero eso da otro error, porque el parámetro de tipo pertenece al trait Into, no al 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 argumentsEn español: “el método recibe 0 argumentos genéricos, pero se pasó 1”. En su lugar, anota la variable, o llama a From directamente sobre el tipo de destino, que además se lee muy bien:
fn main() { let name: String = "Ferris".into(); let city = String::from("Lisboa");
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} vive en {city}"); println!("{big} {also_big} {byte}"); println!("¿300 cabe en un u8? {}", too_big.is_ok());}Salida:
Ferris vive en Lisboa200 200 42¿300 cabe en un u8? falseString::from(...) y u64::from(...) dicen el tipo de destino justo al principio de la línea, así que mucha gente los prefiere a .into() cuando no hay otra pista. Mira Conversiones.
Solución 4: Usar el valor donde su tipo está fijado
Sección titulada «Solución 4: Usar el valor donde su tipo está fijado»Elige esta cuando el valor va a parar de todos modos a un sitio con un tipo conocido, como el parámetro de una función o el campo de un struct. Rust lee la función entera, así que ese uso posterior basta:
fn print_receipt(total: u32, items: Vec<&str>) { println!("{} productos, {} centavos", items.len(), total);}
fn main() { let prices = vec![250, 120, 99]; let total = prices.iter().sum(); // u32, gracias a print_receipt let items = "té,pastel,sopa".split(',').collect(); // Vec<&str>, por la misma razón print_receipt(total, items);}Salida:
3 productos, 469 centavosNo cualquier uso sirve. if prices.iter().sum() > 400 sigue siendo E0283, porque > puede comparar muchas parejas de tipos. Pasar el valor a un parámetro genérico como impl Display tampoco ayuda, porque hay muchos tipos que son Display. El uso tiene que nombrar un único tipo exacto.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente».collect() directamente dentro de println!. println!("{:?}", line.split(',').collect()); da E0283, y rustc te sugiere él mismo la solución: .collect::<Vec<_>>().
.try_into().unwrap(). let small = big.try_into().unwrap(); da E0283 con the type must implement `TryFrom<i64>`. Solución: let small: u8 = big.try_into().unwrap(); o u8::try_from(big).unwrap().
Default::default() pasado a una función genérica. Con fn show(value: impl Debug), la llamada show(Default::default()) es E0283: Rust no sabe el valor por defecto de quién quieres. Solución: show(Settings::default()) o show(u32::default()). En una línea propia, let s = Default::default(); da E0790; mira Default.
Tu propia función genérica. Si un parámetro de tipo solo aparece en el tipo de retorno, como en fn make<T: Default>() -> T, entonces let v = make(); es E0283. Solución: make::<Settings>() o let v: Settings = make();.
Errores relacionados
Sección titulada «Errores relacionados»- E0282: type annotations needed, cuando Rust no tiene ninguna pista sobre el tipo (o lo necesita para llamar a un método).
- E0284: type annotations needed con
cannot satisfy `<_ as FromStr>::Err == _`, casi siempre por.parse(). - E0790:
Default::default()(u otra función de un trait) llamada sin decir de qué tipo. - E0277: nombraste un tipo, pero no se puede construir así, como
let n: Vec<i32> = "1 2".split(' ').collect();. - E0107: número equivocado de argumentos genéricos, que es lo que te da
.into::<String>(). - Todos los errores del compilador: la lista completa de páginas de errores.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»Comprueba lo aprendido
1. ¿Qué te dice la nota de E0283 the type must implement Sum<&i32>?
2. ¿Qué línea arregla let name = "Ferris".into();?
3. ¿Por qué recoger las mismas palabras en un Vec<&str> y en un HashSet<&str> da resultados distintos?
Puntuación: 0 / 3
Para leer más
Sección titulada «Para leer más»- E0283 — Rust error codes index (en inglés): la explicación oficial, con el ejemplo clásico: ¿los
chars()invertidos deberían convertirse en unVec<char>o en unString? - Processing a Series of Items with Iterators — El libro de Rust (en inglés): cómo
sumycollectconsumen un iterador, con ejemplos anotados. También existe una traducción al español de la comunidad del libro. Iterator::collect— documentación de la biblioteca estándar (en inglés): muestra el estilo con anotación y el estilo turbofish uno al lado del otro.Sum— documentación de la biblioteca estándar (en inglés): el trait que hay detrás de.sum(), y la lista completa de tipos que lo implementan.Into— documentación de la biblioteca estándar (en inglés): por qué implementasFromy llamas a.into(), y cómo se elige el tipo de destino.