跳转到内容

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());
}

“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 指向的是哪个变量就好。

看第 2 行下面的标注:

  1. mut scores 下面的 ^^^^^^^^^^:Rust 推断不出类型的就是这个变量。
  2. Vec::new() 下面的 ----------:“type must be known at this point”(到这里必须知道类型)。Vec::new() 创建一个空列表,可它是装什么的空列表?数字?名字?Rust 需要知道,才能准备合适的存储空间。
  3. help 建议写成 let mut scores: Vec<T>。这里的 T 只是个占位符,要换成真正的类型,比如 Vec<u32>。

可以想象邮局里的一个包裹,“内件品名”那一栏是空的。没人填上,柜员就没法寄。你可以自己填,也可以把东西放进去,让柜员看得见。

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 没法替你挑一个。它也不会去猜,因为猜错了会悄悄改变程序的行为。一个函数怎样返回多种类型,见泛型。

大多数情况下选这个。在变量名后面加上 : 类型。写起来很短,还能顺便告诉下一个读代码的人这是什么:

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 去补上那一部分。类型的写法详见数据类型。

当没有变量可以加注解时选这个,比如在一串迭代器调用的中间。用 ::<类型> 直接把类型写在函数调用上。大家把 ::<> 叫做“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);
}

输出:

None
Settings { volume: 0, muted: false }
备份的音量:0
▶ 动手试一试

Settings::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