跳转到内容

Rust 错误 E0284:type annotations needed(parse() 需要类型注解)

E0284 几乎总是只有一个意思:你调用了 .parse(),但 Rust 不知道要把这段文字解析成什么类型。 那条 cannot satisfy `<_ as FromStr>::Err == _` 的说明看起来很吓人,可修复通常只需要几个字符,比如 : u32 或 ::<u32>。本页先给出修复方法,再把这条说明拆开讲清楚,让它不再可怕。这个系列的其他错误,请看 Rust 编译错误详解。

error[E0284]: type annotations needed
--> src/main.rs:2:9
|
2 | let age = "42".parse().unwrap();
| ^^^ ----- type must be known at this point
|
= note: cannot satisfy `<_ as FromStr>::Err == _`
help: consider giving `age` an explicit type
|
2 | let age: /* Type */ = "42".parse().unwrap();
| ++++++++++++

产生这个错误的程序是这样的:

fn main() {
let age = "42".parse().unwrap();
println!("Next year you'll be {}", age + 1);
}

把 .unwrap() 换成 .expect(...)、写成 match "42".parse() { ... },或者在闭包里调用 .parse(),都会看到同样的说明。如果 .parse() 的结果没有存进变量,rustc 会直接指向 parse 本身,并建议你写成 parse::</* Type */>()。

几个标注是最容易看懂的部分:

  1. age 下面的 ^^^:类型未知的那个变量。
  2. parse 下面的 -----:“type must be known at this point”(到这里必须知道类型)。Rust 就卡在这里。
  3. help:“consider giving age an explicit type”(考虑给 age 一个明确的类型)。把 /* Type */ 换成一个真正的类型,比如 u32。

再来一块一块地看那条说明。在编译器的信息里,_ 的意思是“一个我还不知道的类型”:

片段 用大白话说
<_ as FromStr> “你要解析成的那个类型(我还不知道)”
::Err “……以及跟它配套的错误类型”
== _ “必须等于另一个我也不知道的类型”

所以整条说明的意思是:“你没告诉我要解析成什么,所以我连 parse 可能返回什么错误都推断不出来。”

就像你把一封信交给翻译,只说了一句“翻译一下”。翻成法语?还是日语?你不说清楚,他就没法开始,甚至没法告诉你可能会遇到哪些问题。

下面是 parse 的签名,稍微简化了一点:

fn parse<F: FromStr>(&self) -> Result<F, F::Err>

F 是你想要得到的类型,而 F::Err 是它的错误类型。每个目标类型都带着自己的错误类型:解析 i32 可能失败并返回 ParseIntError,解析 f64 对应 ParseFloatError,解析 bool 对应 ParseBoolError。Rust 必须先知道 F,才能知道 parse 到底返回什么。

这个选择还会影响运行时的结果。"42" 解析成 u8、i32 或 f64 都没问题。但 "300" 解析成 u8 会失败(太大了),"4.5" 解析成 i32 也会失败(不是整数)。Rust 不会去猜,因为猜错了就会变成一个只在特定输入下才出现的 bug。类型转换这一课讲了 FromStr 怎样工作,以及怎样给你自己的类型实现它。

Rust 怎样自己推断类型,在 E0282 页面里有讲解。为什么“type annotations needed”有三个不同的错误代码(E0282、E0283、E0284),在 E0283 页面里有解释。简单来说:E0284 指的是卡住的那条规则涉及一个属于未知类型的类型(这里是 F::Err)。对你来说,修法都一样:把类型写出来。

大多数情况下就选这个。这是 Rust 程序设计语言(the Rust Book)用的写法,而且它顺便说明了这段文字应该是什么内容:

fn main() {
let age: u32 = "42".parse().unwrap();
let price: f64 = "4.99".parse().unwrap();
let port: u16 = "8080".parse().expect("端口必须是数字");
println!("明年你就 {} 岁了", age + 1);
println!("两杯咖啡:{:.2}", price * 2.0);
println!("正在监听端口 {port}");
}

输出:

明年你就 43 岁了
两杯咖啡:9.98
正在监听端口 8080
▶ 动手试一试

选一个符合文字含义的类型:端口用 u16,百分比用 u8,价格用 f64。类型太小的话,合法的输入也会变成错误,所以拿不准时,就选一个范围更大的类型,比如 i64 或 u32。

修复 2:用 turbofish 写法 parse::<T>()

Section titled “修复 2:用 turbofish 写法 parse::<T>()”

当没有变量可以加注解时就选这个:在闭包里、在迭代器链的中间,或者在 ... * 2 这样的表达式里:

fn main() {
let line = "3 7 5";
let total = line
.split(' ')
.map(|s| s.parse::<u32>().unwrap())
.sum::<u32>();
let doubled = "21".parse::<i32>().unwrap() * 2;
println!("总计:{total}");
println!("翻倍:{doubled}");
}

输出:

总计:15
翻倍:42
▶ 动手试一试

把 turbofish 写在 parse 本身上。在链式调用里,只给最终结果加注解往往不够,见下面“常见场景”里的第一条。

修复 3:写明类型,并好好处理错误

Section titled “修复 3:写明类型,并好好处理错误”

处理真实输入(文件、命令行参数、网页表单)时选这个,因为遇到不合法的文字,.unwrap() 会让程序崩溃。在 parse 上写明类型,再对 Result 做 match。注意看类型怎样决定哪些输入能成功:

fn main() {
for input in ["42", "300", "abc"] {
match input.parse::<u8>() {
Ok(n) => println!("{input}:得到 {n}"),
Err(e) => println!("{input}:{e}"),
}
}
let rate: Result<f64, _> = "0.25".parse();
println!("{:?}", rate);
}

输出:

42:得到 42
300:number too large to fit in target type
abc:invalid digit found in string
Ok(0.25)
▶ 动手试一试

错误信息是标准库给出的英文:“number too large to fit in target type”是数字太大、目标类型放不下;“invalid digit found in string”是字符串里有不合法的数字字符。在 Result<f64, _> 里,你只写出了值的类型。_ 让 Rust 自己填上错误类型(ParseFloatError),而这正是那条说明在问的部分。更多内容见 Result。

修复 4:在类型已经确定的地方使用这个值

Section titled “修复 4:在类型已经确定的地方使用这个值”

当解析出来的值要放进一个类型已知的结构体字段或函数参数时,就选这个。Rust 会读完整个函数,后面的用法就回答了它的问题:

struct Config {
port: u16,
workers: u8,
}
fn greet(age: u32) {
println!("欢迎!你今年 {age} 岁。");
}
fn main() {
let port = "8080".parse().unwrap(); // u16,来自 Config
let workers = "4".parse().unwrap(); // u8,来自 Config
let config = Config { port, workers };
println!("端口 {},{} 个工作线程", config.port, config.workers);
let age = "42".parse().unwrap(); // u32,来自 greet
greet(age);
}

输出:

端口 8080,4 个工作线程
欢迎!你今年 42 岁。
▶ 动手试一试

不是每种用法都算数,所以它排在第 4 个。像 age + 1 这样的运算和 age >= limit 这样的比较仍然会报 E0284,哪怕 limit 是 u32:运算符可以用在很多种类型组合之间,所以它们说不出一个确切的类型。而调用方法,比如 age.pow(2),会把错误变成 E0282。拿不准时,就用修复 1。

在 map 里解析,然后 sum()。 let total: u32 = line.split(' ').map(|s| s.parse().unwrap()).sum(); 仍然是 E0284,尽管 total 有类型。一个 u32 既可以由 u32 值求和得到,也可以由 &u32 值求和得到,所以这个注解没法告诉 Rust 每一项是什么。修复:s.parse::<u32>()。(改成 collect 到 Vec<i32> 倒是可以,因为 Vec<i32> 只能装 i32。)

把解析结果收集成一个 Result。 let nums: Result<Vec<_>, _> = text.split(' ').map(|x| x.parse()).collect(); 是 E0284:Vec<_> 里的 _ 让元素类型悬而未决。修复:Result<Vec<i32>, _>。这个好用的写法会在第一个不合法的元素处停下,并把它的错误交给你。

读取用户输入。 在猜数字游戏或命令行工具里写 let guess = line.trim().parse().unwrap();。修复:let guess: u32 = ...,和 Rust 程序设计语言里写的一模一样。

用了 ? 会得到一个更奇怪的错误。 let port = text.parse()?; 不会报 E0284。在 2021 edition 里,Rust 会退而使用 () 类型,然后报 E0277:the trait bound `(): FromStr` is not satisfied(2024 edition 里显示的是 ! 而不是 ())。修法一样:let port: u16 = text.parse()?;。

  • E0282:type annotations needed,完全没有线索时出现(或者调用 age.pow(2) 这类方法时需要知道类型)。
  • E0283:在 .sum()、.collect() 或 .into() 之后需要类型注解,因为有很多类型都符合。那一页也解释了为什么会有三个不同的错误代码。
  • E0790:调用 FromStr::from_str("42") 或 Default::default() 时没有说明是哪个类型。
  • E0277:不写类型时 .parse()? 给出的错误,以及你选的类型没法从文字解析时得到的错误。
  • E0599:找不到方法,比如在不是文字的东西上调用 .parse(),像一个 u32 变量(这时改用 u8::try_from(n) 来转换)。
  • 所有编译错误:错误页面的完整列表。

小测验

1. 用大白话说,cannot satisfy <_ as FromStr>::Err == _ 是什么意思?

2. 哪一行能修复 let port = "8080".parse().unwrap();?

3. let total: u32 = line.split(' ').map(|s| s.parse().unwrap()).sum(); 仍然报 E0284。类型应该写在哪里?

得分: 0 / 3