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 */>()。
这个错误是什么意思
Section titled “这个错误是什么意思”几个标注是最容易看懂的部分:
age下面的^^^:类型未知的那个变量。parse下面的-----:“type must be known at this point”(到这里必须知道类型)。Rust 就卡在这里。- help:“consider giving
agean explicit type”(考虑给age一个明确的类型)。把/* Type */换成一个真正的类型,比如u32。
再来一块一块地看那条说明。在编译器的信息里,_ 的意思是“一个我还不知道的类型”:
| 片段 | 用大白话说 |
|---|---|
<_ as FromStr> |
“你要解析成的那个类型(我还不知道)” |
::Err |
“……以及跟它配套的错误类型” |
== _ |
“必须等于另一个我也不知道的类型” |
所以整条说明的意思是:“你没告诉我要解析成什么,所以我连 parse 可能返回什么错误都推断不出来。”
就像你把一封信交给翻译,只说了一句“翻译一下”。翻成法语?还是日语?你不说清楚,他就没法开始,甚至没法告诉你可能会遇到哪些问题。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”下面是 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)。对你来说,修法都一样:把类型写出来。
修复 1:给变量加上类型注解
Section titled “修复 1:给变量加上类型注解”大多数情况下就选这个。这是 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:得到 42300:number too large to fit in target typeabc:invalid digit found in stringOk(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
- E0284 — Rust error codes index(英文):这个错误的官方解释。
str::parse— 标准库文档(英文):这个方法的签名,类型注解和 turbofish 两种写法都有示例。FromStr— 标准库文档(英文):parse背后的 trait,包括它的Err类型以及怎样实现它。- 写一个猜数字游戏 — Rust 程序设计语言:
let guess: u32 = guess.trim().parse()第一次出现的地方,一步步讲解。 - 数据类型 — Rust 程序设计语言:为什么
"42".parse()需要类型注解,附有真实的编译器报错。