Rust 错误 E0282:type annotations needed(需要类型注解)
E0282 的意思是:Rust 想自己推断出一个类型,却没找到足够的线索。你的逻辑没有任何问题,编译器只是需要你说出一个类型,通常多写几个字符就够了。本页先给出修法,再解释 Rust 是怎样猜类型的(这叫类型推断),这样你就知道什么时候该帮它一把。这个系列的其他错误,请看 Rust 编译错误详解。
error[E0282]: type annotations needed for `Vec<_>` --> src/main.rs:2:9 |2 | let mut scores = Vec::new(); // a Vec of... what? | ^^^^^^^^^^ ---------- 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(); // a Vec of... what? | ++++++++第一行的意思是“Vec<_> 需要类型注解”。help 建议你“给 scores 写上明确的类型,并指定类型参数 T”。
产生这个错误的程序是这样的:
fn main() { let mut scores = Vec::new(); // a Vec of... what? println!("Scores recorded: {}", scores.len());}同样的信息,不同的代码
Section titled “同样的信息,不同的代码”“type annotations needed”这几个字还会和另外三个错误代码一起出现。它们说的是同一件事,本页的修法对它们都管用:
- E0283:
.collect()或.sum()没有目标类型。rustc 会加上一条类似the type must implement `FromIterator<&str>`或`Sum<&i32>`的 note,因为有很多类型都可以是结果。 - E0284:
"42".parse().unwrap()没有目标类型。note 写的是cannot satisfy `<_ as FromStr>::Err == _`,听着吓人,其实就是在问“要解析成什么?”。 - E0790:
let settings = Default::default();。标题写的是cannot call associated function on trait without specifying the corresponding `impl` type(没有指定对应的impl类型,就不能调用 trait 上的关联函数):Rust 不知道你要的是谁的默认值。
具体得到哪个代码,取决于那一行怎么写,而且你多写几行代码后它还可能变。比如 let age = "42".parse().unwrap(); 会得到 E0284,但如果下一行调用了 age.pow(2),就变成 E0282。不用纠结编号,看 rustc 指向的是哪个变量就好。
这个错误是什么意思
Section titled “这个错误是什么意思”看第 2 行下面的标注:
mut scores下面的^^^^^^^^^^:Rust 推断不出类型的就是这个变量。Vec::new()下面的----------:“type must be known at this point”(到这里必须知道类型)。Vec::new()创建一个空列表,可它是装什么的空列表?数字?名字?Rust 需要知道,才能准备合适的存储空间。- help 建议写成
let mut scores: Vec<T>。这里的T只是个占位符,要换成真正的类型,比如Vec<u32>。
可以想象邮局里的一个包裹,“内件品名”那一栏是空的。没人填上,柜员就没法寄。你可以自己填,也可以把东西放进去,让柜员看得见。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”Rust 里的每个值都有一个确切的类型,在编译时就定下来了。大多数时候你不用写出来,因为 Rust 会读你的代码,自己推断。let count = 5; 是 i32,let name = String::from("Ferris"); 是 String。这就是类型推断,变量那一课有更多例子。
推断用的是整个函数里的线索,不只是一行。如果你后面调用了 scores.push(90),这就是一条线索:这个 Vec 装的是数字。如果你把 scores 传给一个接收 &[u32] 的函数,线索就更明确了。E0282 出现在完全没有线索的时候,或者 Rust 在碰到线索之前就需要知道类型的时候(比如要查找某个方法)。
有些函数的返回类型是泛型的:parse、collect、sum、Default::default、Vec::new、None。它们可以产生很多种类型,所以 Rust 没法替你挑一个。它也不会去猜,因为猜错了会悄悄改变程序的行为。一个函数怎样返回多种类型,见泛型。
修复 1:给变量加上类型注解
Section titled “修复 1:给变量加上类型注解”大多数情况下选这个。在变量名后面加上 : 类型。写起来很短,还能顺便告诉下一个读代码的人这是什么:
fn main() { let scores: Vec<u32> = Vec::new(); let age: u32 = "42".parse().unwrap(); let words: Vec<&str> = "red green blue".split(' ').collect(); let winner: Option<String> = None;
println!("已记录的分数:{} 个", scores.len()); println!("明年你就 {} 岁了", age + 1); println!("{:?}", words); println!("{:?}", winner);}输出:
已记录的分数:0 个明年你就 43 岁了["red", "green", "blue"]None你不一定要写出完整的类型。let words: Vec<_> = ...collect(); 的意思是“一个 Vec,里面装什么你自己推断”。_ 让 Rust 去补上那一部分。类型的写法详见数据类型。
修复 2:用 turbofish ::<>
Section titled “修复 2:用 turbofish ::<>”当没有变量可以加注解时选这个,比如在一串迭代器调用的中间。用 ::<类型> 直接把类型写在函数调用上。大家把 ::<> 叫做“turbofish”(涡轮鱼),因为 ::<> 看起来有点像一条鱼:
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}");}输出:
[4, 15, 7]合计:26最常见的三种 turbofish 调用是 parse::<i32>()、collect::<Vec<_>>() 和 sum::<i32>()。修复 1 和修复 2 一样正确,let total: u32 = numbers.iter().sum(); 做的是同一件事。哪种读起来顺就用哪种。
修复 3:用上这个值,让 Rust 推断类型
Section titled “修复 3:用上这个值,让 Rust 推断类型”当错误只是因为你的代码还没写完时选这个。常见的情况是:你先创建了一个空 Vec,往里面放东西的代码还在后面。一旦用上了这个值,Rust 就有了线索,错误随之消失,不需要任何注解:
fn print_team(names: &[String]) { for name in names { println!("- {name}"); }}
fn main() { let mut team = Vec::new(); // 类型未知……暂时而已 team.push(String::from("Ferris")); // 现在 Rust 知道了:Vec<String> team.push(String::from("Corro")); print_team(&team);}输出:
- Ferris- Corro顺序有一点讲究:线索必须出现在 Rust 为了调用方法而需要类型之前。像 let shout = |s| s.to_uppercase(); 这样的闭包,就算下一行就调用了 shout("hi"),也还是会失败,因为 Rust 得先知道 s 是什么,才能找到 .to_uppercase()。这时就写上类型:|s: &str| s.to_uppercase()。参见闭包。
修复 4:给 None 和 Default::default() 写明类型
Section titled “修复 4:给 None 和 Default::default() 写明类型”对于本身完全不带线索的值,选这个。None 没说它会装什么,Default::default() 也没说你要的是谁的默认值。把类型写进表达式里:
#[derive(Debug, Default)]struct Settings { volume: u8, muted: bool,}
fn main() { let fastest_lap = None::<f64>; // 而不是:None let settings = Settings::default(); // 而不是:Default::default() let backup: Settings = Default::default(); // 加类型注解也行
println!("{:?}", fastest_lap); println!("{:?}", settings); println!("备份的音量:{}", backup.volume);}输出:
NoneSettings { volume: 0, muted: false }备份的音量:0Settings::default() 是最清楚的写法,一看就是“Settings 的默认值”。None::<f64> 很方便,但 let fastest_lap: Option<f64> = None;(修复 1)更常见。参见 Option。
把文本解析成数字。 let guess = line.trim().parse().unwrap(); 会得到 E0284(如果下一行就在 guess 上调用方法,则是 E0282)。parse 可以产生 i32、u8、f64 等很多类型。修复:let guess: i32 = ... 或 .parse::<i32>()。之后再拿 guess 和一个 i32 变量比较,在这里是不够的(把它传给接收 i32 的函数倒是可以),所以最简单的修法就是在调用处直接写明类型。E0284 详细讲了这种情况。
.collect()。 它能构建 Vec、String、HashSet、HashMap……所以总是需要知道目标类型。修复:let words: Vec<_> = ...collect(); 或 .collect::<Vec<_>>()。参见迭代器。
.sum() 和 .product()。 nums.iter().sum() 会得到 E0283,并提示 the type must implement `Sum<&i32>`。把一堆 i32 加起来可以得到 i32,但别的数字类型也可以是和。修复:let total: i32 = nums.iter().sum(); 或 .sum::<i32>()。
结构体构造函数里的空集合。 在一个还没写完的 new() 函数里写了 let cache = HashMap::new();。修复:把完整类型写一次(HashMap<String, u32>),或者把它放进一个字段类型已经声明好的结构体里返回,这就是 Rust 能用的线索。
- E0308:类型不匹配。当你的注解和值对不上时就会出现,比如
let age: u32 = "42";。 - E0277:不满足 trait 约束。你给出了类型,但没法用这种方式构建它,比如
let n: Vec<i32> = "1 2".split(' ').collect();(这得到的是Vec<&str>,要先把每一项解析一下)。 - E0599:找不到方法,比如你注解了
let n: u32,却在它上面调用.trim()这种String的方法。 - E0004:模式没有穷尽,又一个 rustc 会替你写好修法的错误。
- 所有编译错误:错误页面的完整列表。
小测验
1. let ids = Vec::new(); 报了 E0282,而这个 Vec 本来要装 u64 值。哪一行能修好它?
2. 文本明明就是个数字,为什么 let n = "7".parse().unwrap(); 还需要类型?
3. .collect::<Vec<_>>() 里的 _ 是什么意思?
得分: 0 / 3
- E0282 — Rust error codes index(英文):这个错误的官方解释,附有更多示例。
- E0283 — Rust error codes index(英文):用
collect和sum时、有多种类型都符合时会看到的变体。 - 数据类型 — Rust 程序设计语言:讲解类型注解,包括经典的
"42".parse()例子。 - 类型推断 — 通过例子学 Rust:一个简短示例,展示 Rust 怎样根据后面的代码推断出
Vec的类型。 Iterator::collect— 标准库文档(英文):同时展示了类型注解和 turbofish 两种写法。