Rust 错误 E0283:type annotations needed(sum、collect、into 需要类型注解)
E0283 的意思是:Rust 知道你的值必须能做什么,但能做到这一点的类型有很多,而 Rust 不会替你挑一个。你通常会在 .sum()、.collect() 或 .into() 之后马上碰到它。这是最好修的错误之一:写明你想要的类型,就搞定了。这个系列的其他错误,请看 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 othersnote: required by a bound in `std::iter::Iterator::sum`help: consider giving `total` an explicit type |3 | let total: /* Type */ = prices.iter().sum(); | ++++++++++++第一行的意思是“需要类型注解”。最后的 help: 建议你“给 total 写一个明确的类型”。
产生这个错误的程序是这样的:
fn main() { let prices = vec![250, 120, 99]; // 价格,单位是分 let total = prices.iter().sum(); println!("总计:{total} 分");}.collect() 和 .into() 会报同样的错误,只是 note 不一样。对于 let words = text.split(' ').collect();,note 是 the type must implement `FromIterator<&str>`。对于 let name = "Ferris".into();,note 是 the type must implement `From<&str>`。
这个错误是什么意思
Section titled “这个错误是什么意思”从上往下读这条信息:
total下面的^^^^^: 这就是 Rust 没法确定类型的那个变量。sum下面的---: “type must be known at this point”(到这里必须知道类型)。sum可以返回很多种不同的类型,所以 Rust 需要在这里得到答案。- note:
the type must implement `Sum<&i32>`(这个类型必须实现Sum<&i32>)。这是 Rust 找到的规则:“total必须是一个能把一堆&i32加起来得到的类型”。 - help 列表: 那些知道怎样成为“某种东西之和”的类型。它们有很多:每一种数字类型、
Duration,甚至还有Option和Result。Rust 列出了 8 个,然后说“and 88 others”(还有 88 个)。人们说的 multiple impls satisfy this bound(有多个实现满足这个约束)就是这个意思:不止一个类型有符合这条规则的impl。
想象你在咖啡馆点了“随便来点喝的”。这是一条真实的规则(必须能喝),但茶、果汁和水都符合。你不选,服务员就什么也端不上来。E0283 就是 Rust 端着托盘,在等你的答复。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”sum、collect 和 into 在返回类型上是泛型的。你要的类型,决定了这个函数做什么。trait(Sum、FromIterator、From)只是规则。所以当没有任何东西告诉 Rust 类型时,它只有规则,没有唯一的答案。
而答案很重要。把同样的单词收集进 Vec<&str> 会保留重复项,收集进 HashSet<&str> 则会去掉重复项。求和得到 u8 时,超过 255 就会溢出,而 u64 不会。如果 Rust 替你挑了一个类型,别处一个小小的改动,就可能悄悄改变程序的行为。Rust 宁愿问你一次。
Rust 怎样自己推断类型(类型推断),在 E0282 页面里讲过了,这里不再重复。这些方法背后的 trait,参见 trait 和泛型。
E0282、E0283、E0284:同一个问题,三个代码
Section titled “E0282、E0283、E0284:同一个问题,三个代码”三者说的都是 “type annotations needed”(需要类型注解),对你来说意思也一样:写出类型。代码只是告诉你,Rust 卡住的时候正在做什么:
- E0282: Rust 对类型完全没有线索,或者它在拿到线索之前就需要知道类型(比如要查找
.len()这样的方法)。 - E0283(本页): Rust 有一条以 trait 规则形式出现的线索(
must implement `Sum<&i32>`),但有很多类型都符合这条规则。 - E0284: 规则涉及的是属于那个未知类型的另一个类型,比如
parse的错误类型(cannot satisfy `<_ as FromStr>::Err == _`)。在知道主类型之前,Rust 没法检查它。
代码甚至会在你打字的时候变来变去。let words = text.split(' ').collect(); 后面跟着 println!("{:?}", words),报的是 E0283。把下一行改成 words.len(),就变成了 E0282,因为现在 Rust 需要知道类型才能找到方法。下面的修法对这三个错误都管用。
修复 1:给变量加上类型注解
Section titled “修复 1:给变量加上类型注解”大多数时候都选这个方法。在变量名后面加上 : Type。写起来很短,而且能让下一个读代码的人一眼看出你在构建什么:
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} 分"); println!("{:?}", list); println!("不重复的标签:{} 个", unique.len());}输出:
总计:469 分["rust", "web", "rust", "cli"]不重复的标签:3 个同样是 collect(),却得到两种不同的结果:你写下的类型就是给它的指令。里面的部分可以用 _ 交给 Rust 推断,比如 let list: Vec<_> = ...。
修复 2:在 sum 和 collect 上用 turbofish
Section titled “修复 2:在 sum 和 collect 上用 turbofish”当没有变量可以加注解时,选这个方法:比如在 println! 里面、迭代器链的中间,或者作为函数参数。用 ::<Type>(也就是“turbofish”)把类型写在调用上:
fn main() { let prices = vec![250, 120, 99]; println!("总计:{} 分", 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!("首字母:{initials}");}输出:
总计:469 分["tea", "cake", "soup"]首字母:tcs这里的单词保留了英文,因为 &s[..1] 按字节截取第一个字符,而一个汉字占 3 个字节。.product::<u64>() 的用法也一样。修复 1 和修复 2 同样正确,选读起来更顺的那个就好。
修复 3:对于 .into(),写明目标类型(turbofish 不管用)
Section titled “修复 3:对于 .into(),写明目标类型(turbofish 不管用)”用 .into() 和 .try_into() 时,选这个方法。你可能很想写 "Ferris".into::<String>(),但那会得到另一个错误,因为类型参数属于 Into trait,而不属于这个方法:
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这条错误的意思是“这个方法不接受泛型参数,你却给了一个”。正确的做法是给变量加注解,或者直接在目标类型上调用 From,这样读起来也很自然:
fn main() { let name: String = "Ferris".into(); let city = String::from("Lisbon");
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} 住在 {city}"); println!("{big} {also_big} {byte}"); println!("300 能放进 u8 吗?{}", too_big.is_ok());}输出:
Ferris 住在 Lisbon200 200 42300 能放进 u8 吗?falseString::from(...) 和 u64::from(...) 在一行的开头就说明了目标类型,所以在没有其他线索时,很多人更喜欢用它们,而不是 .into()。参见类型转换。
修复 4:在类型固定的地方使用这个值
Section titled “修复 4:在类型固定的地方使用这个值”当这个值本来就要传到某个类型已知的地方时(比如函数参数或结构体字段),选这个方法。Rust 会通读整个函数,所以后面的这次使用就足够了:
fn print_receipt(total: u32, items: Vec<&str>) { println!("{} 件商品,{} 分", items.len(), total);}
fn main() { let prices = vec![250, 120, 99]; let total = prices.iter().sum(); // 是 u32,多亏了 print_receipt let items = "tea,cake,soup".split(',').collect(); // 是 Vec<&str>,原因相同 print_receipt(total, items);}输出:
3 件商品,469 分不是每种使用都算数。if prices.iter().sum() > 400 仍然是 E0283,因为 > 可以比较很多种类型的组合。把值传给像 impl Display 这样的泛型参数也没用,因为很多类型都是 Display 的。这次使用必须指明唯一一个确切的类型。
把 .collect() 直接放进 println!。 println!("{:?}", line.split(',').collect()); 会报 E0283,而 rustc 自己就会给出修法:.collect::<Vec<_>>()。
.try_into().unwrap()。 let small = big.try_into().unwrap(); 会报 E0283,并附上 the type must implement `TryFrom<i64>`。修复:let small: u8 = big.try_into().unwrap();,或者 u8::try_from(big).unwrap()。
把 Default::default() 传给泛型函数。 对于 fn show(value: impl Debug),调用 show(Default::default()) 会报 E0283:Rust 不知道你要的是谁的默认值。修复:show(Settings::default()) 或 show(u32::default())。如果单独写一行 let s = Default::default();,得到的则是 E0790;参见 Default。
你自己写的泛型函数。 如果类型参数只出现在返回类型里,比如 fn make<T: Default>() -> T,那么 let v = make(); 就是 E0283。修复:make::<Settings>(),或者 let v: Settings = make();。
- E0282:需要类型注解,发生在 Rust 对类型完全没有线索(或者调用方法时需要知道类型)的时候。
- E0284:需要类型注解,并附有
cannot satisfy `<_ as FromStr>::Err == _`,几乎总是来自.parse()。 - E0790:调用
Default::default()(或其他 trait 函数)时没有说明是哪个类型。 - E0277:你写明了类型,但它没法用这种方式构建,比如
let n: Vec<i32> = "1 2".split(' ').collect();。 - E0107:泛型参数的个数不对,
.into::<String>()给你的就是这个错误。 - 所有编译错误:错误页面的完整列表。
小测验
1. E0283 的 note the type must implement Sum<&i32> 告诉了你什么?
2. 哪一行能修好 let name = "Ferris".into();?
3. 为什么把同样的单词收集进 Vec<&str> 和 HashSet<&str> 会得到不同的结果?
得分: 0 / 3
- E0283 — Rust error codes index(英文):这个错误的官方说明,附有一个经典例子:反转后的
chars()应该变成Vec<char>还是String? - 使用迭代器处理元素序列 — Rust 程序设计语言:
sum和collect怎样消费迭代器,附有带类型注解的示例。 Iterator::collect— 标准库文档(英文):把类型注解写法和 turbofish 写法放在一起对比。Sum— 标准库文档(英文):.sum()背后的 trait,以及实现了它的全部类型。Into— 标准库文档(英文):为什么要实现From再调用.into(),以及目标类型是怎样选出来的。