Rust 错误 E0005:refutable pattern in local binding(let 中的可反驳模式)
E0005 的意思是:你写的 let 用了一个可能匹配不上的模式,比如 value 有可能是 None,你却写了 let Some(x) = value;。Rust 需要知道匹配不上时该怎么办,而普通的 let 没有地方让你写这个。修法就是给“没匹配上”的情况找个去处:通常是 let ... else、if let 或 match。刚开始用 Option 和 Result 时,这个错误非常常见,修起来一分钟就够了。这个系列的其他错误,请看 Rust 编译错误详解。
error[E0005]: refutable pattern in local binding --> src/main.rs:4:9 |4 | let Some(top) = best; // ❌ what if the list was empty? | ^^^^^^^^^ pattern `None` not covered | = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `Option<i32>`help: you might want to use `let...else` to handle the variant that isn't matched |4 | let Some(top) = best else { todo!() }; // ❌ what if the list was empty? | ++++++++++++++++第一行的意思是“局部绑定中出现了可反驳的模式”。第一条 note 说:let 绑定需要“不可反驳的模式”,比如结构体,或者只有一个变体的枚举。help 建议你“用 let...else 处理没匹配上的那个变体”。
产生这个错误的程序是这样的:
fn main() { let scores = vec![72, 95, 88]; let best = scores.iter().max().copied(); let Some(top) = best; // ❌ what if the list was empty? println!("Top score: {top}");}换成别的模式,你也会看到同样的错误,变的只有“not covered”(没有覆盖到)那一部分:
3 | let Ok(age) = input.parse::<i32>(); | ^^^^^^^ pattern `Err(_)` not covered
3 | let [host, port] = parts.as_slice(); | ^^^^^^^^^^^^ patterns `&[]`, `&[_]` and `&[_, _, _, ..]` not covered这个错误是什么意思
Section titled “这个错误是什么意思”关键在两个词:
- 可反驳(refutable)的模式可能匹配失败。
Some(top)在值为None时失败,Ok(age)遇到Err时失败,[host, port]在 slice 不是正好两个元素时失败。 - 不可反驳(irrefutable)的模式总能匹配。像
let top = best;这样只写一个名字总是行得通的,元组和结构体也一样,比如let (x, y) = point;。
let 必须总是成功,因为下一行要用到它创建的变量。如果 best 是 None,下一行的 top 该是什么?没有答案,所以 Rust 拦住了你。“not covered”标注准确列出了你漏掉的情况:None、Err(_),或者对 slice 来说,“空的”“只有一个元素”和“三个或更多元素”。
可以想象一个快递柜。let Some(top) = best; 就像你连柜门都没打开,就笃定里面有包裹,转身走了。Rust 要求你先想好:柜子是空的话怎么办。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”let、函数参数和 for 循环都需要不会失败的模式。match、if let、while let 和 let ... else 可以接受会失败的模式,因为它们都有地方处理另一种情况。这和 E0004 是同一条规则:match 必须覆盖所有的值。在这里,“match”就是一个只有一个分支的 let。
在很多语言里,读取一个不存在的值,会得到 null、undefined,或者在运行时崩溃。而在 Rust 里,空列表、文本框里填错的数字、缺少的设置项,都是要在代码里处理的正常情况。Option 和 Result 让它们一目了然,E0005 则确保你不会不小心跳过它们。参见在 let 和函数参数里使用模式和 Option。
修复 1:用 let ... else 提前离开
Section titled “修复 1:用 let ... else 提前离开”当函数剩下的部分需要这个值、没有它就没什么可做时,选这个。这也是 rustc 的 help: 给出的建议。模式保持不变,再加一个 else 块。这个块必须离开当前代码:return、break、continue 或者 panic。
fn port_from(text: &str) -> u16 { // 把数字取出来,取不出来就提前离开函数 let Ok(port) = text.trim().parse::<u16>() else { println!("“{text}”不是有效的端口,改用 8080"); return 8080; }; port}
fn main() { let scores = vec![72, 95, 88]; let Some(top) = scores.iter().max() else { println!("还没有分数"); return; }; println!("最高分:{top}");
println!("端口:{}", port_from("3000")); println!("端口:{}", port_from("abc"));}输出:
最高分:95端口:3000“abc”不是有效的端口,改用 8080端口:8080别把 rustc 给的 else { todo!() } 原样留着。todo!() 一运行就会 panic,要把它换成真正的退出方式。详见 let-else。
修复 2:两种情况都要做事时,用 match 或 if let
Section titled “修复 2:两种情况都要做事时,用 match 或 if let”当“没匹配上”的情况有自己的事要做(比如打印错误),或者无论哪种情况代码都要继续往下走时,选这个。match 让你处理每一种情况,还能用上错误值。不知道 slice 有多长时,也要用它来拆开 slice。只关心其中一种情况时,就用 if let:
fn main() { // match:两种结果都处理,还能用上错误 for input in ["42", "forty-two"] { match input.parse::<i32>() { Ok(age) => println!("年龄:{age}"), Err(e) => println!("无法读取“{input}”:{e}"), } }
// slice 可以是任意长度:按它的形状来匹配 for address in ["localhost:8080", "localhost"] { let parts: Vec<&str> = address.split(':').collect(); match parts.as_slice() { [host, port] => println!("host = {host}, port = {port}"), _ => println!("“{address}”应该写成 host:port 的形式"), } }
// if let:只在有值的时候做事 let nickname: Option<&str> = None; if let Some(name) = nickname { println!("你好,{name}!"); } else { println!("你好,陌生人!"); }}输出:
年龄:42无法读取“forty-two”:invalid digit found in stringhost = localhost, port = 8080“localhost”应该写成 host:port 的形式你好,陌生人!invalid digit found in string(字符串里有无效的数字)是标准库自己的错误信息,所以保持英文。_ 分支兜住了其他所有长度。没有它,match 会给你 E0004,附上同样的“not covered”列表。参见 if let 和 match。
修复 3:用 unwrap_or 给默认值,或者用 ? 交给调用方
Section titled “修复 3:用 unwrap_or 给默认值,或者用 ? 交给调用方”当有一个合理的默认值,或者应该让调用方去处理问题时,选这个。unwrap_or(x) 有值就给你这个值,没有就给 x。? 用在返回 Result(或 Option)的函数里:它把错误交还给调用方,并结束当前函数。
use std::num::ParseIntError;
// ? 把错误交给调用方,而不是在这里处理fn total_minutes(hours: &str, minutes: &str) -> Result<i32, ParseIntError> { let h = hours.parse::<i32>()?; let m = minutes.parse::<i32>()?; Ok(h * 60 + m)}
fn main() { // 什么都没有时,用默认值 let scores: Vec<i32> = Vec::new(); let top = scores.iter().max().copied().unwrap_or(0); println!("最高分:{top}");
let volume = "loud".parse::<u8>().unwrap_or(50); println!("音量:{volume}");
println!("{:?}", total_minutes("2", "30")); println!("{:?}", total_minutes("2", "half"));}输出:
最高分:0音量:50Ok(150)Err(ParseIntError { kind: InvalidDigit })如果你确定值一定存在(比如是你刚在代码里写下的数字),用 .expect("原因") 也可以。万一你判断错了,它会带着你的信息 panic,所以只把它留给真的不会失败的情况。在什么都不返回的 main 里用 ?,会得到 E0277;参见 ? 运算符和快捷写法:unwrap_or 和 unwrap。
修复 4:换一个模式不会失败的类型
Section titled “修复 4:换一个模式不会失败的类型”当数据的形状总是一样、模式失败只是因为你选的类型不合适时,选这个。slice 或 Vec 可以是任意长度,但 [u8; 3] 这样的数组总是正好有三个元素,所以 let [r, g, b] = color; 总能匹配。元组和结构体也总能匹配。还有 split_once,它把前后两半放在一个元组里返回,这样你只需要处理一个 Option,而不是一个长度未知的 slice:
struct Point { x: i32, y: i32,}
fn main() { // 数组长度固定:这个模式总能匹配 let color: [u8; 3] = [255, 140, 0]; let [r, g, b] = color; println!("r={r} g={g} b={b}");
// 元组和结构体也总能匹配 let (host, port) = ("localhost", 8080); let Point { x, y } = Point { x: 3, y: -1 }; println!("{host}:{port} 位于 ({x}, {y})");
// split_once 返回一个装着前后两半的 Option let Some((key, value)) = "theme=dark".split_once('=') else { return; }; println!("{key} -> {value}");}输出:
r=255 g=140 b=0localhost:8080 位于 (3, -1)theme -> dark你自己定义的枚举也一样:let Shape::Circle(radius) = shape; 会失败,并提示 pattern `Shape::Square(_)` not covered。如果这个值真的只可能是圆,那它也许应该是一个 Circle 结构体,而不是枚举。参见数组和结构体。
用 for 循环遍历一组 Option。 for Some(temp) in readings 会得到“refutable pattern in for loop binding”(for 循环绑定中的可反驳模式)。修复:for temp in readings.iter().flatten() 会跳过所有 None;或者遍历每个元素,在循环里用 if let Some(t) = reading。
函数参数。 fn greet(Some(name): Option<&str>) 会得到“refutable pattern in function argument”(函数参数中的可反驳模式)。修复:接收 name: Option<&str>,在函数里 match;或者接收 name: &str,让调用方决定遇到 None 怎么办。
解析输入。 读取用户输入或命令行参数时写了 let Ok(n) = line.trim().parse::<i32>();。修复:在输入循环里写 let Ok(n) = ... else { println!("请输入一个数字"); continue; };,或者用 match 把错误打印出来。
- E0004:模式没有穷尽:同样的“not covered”检查,只不过针对的是
match,而不是let。 - E0308:类型不匹配:模式和类型根本对不上,比如
let Some(x) = 5;。 - E0277:不满足 trait 约束:在不返回
Result或Option的函数里(比如普通的fn main())用了?,就会得到它。 - E0282:需要类型注解:常和
parse()一起出现,这时 Rust 看不出你想要什么类型。像上面的例子那样加上::<i32>就行。 - 所有编译错误:错误页面的完整列表。
小测验
1. 下面哪个 let 会得到 E0005?
2. let ... else 的 else 块必须做什么?
3. 你要从文本里取出一个数字,如果文本不是数字,用 0 作为默认值就行。最短的修法是什么?
得分: 0 / 3
- E0005 — Rust error codes index(英文):这个错误的官方解释。
- Refutability(可反驳性):模式是否会匹配失效 — Rust 程序设计语言:rustc 报错里链接的那一章(中文版),讲解会失败和不会失败的模式。
- 所有可能会用到模式的位置 — Rust 程序设计语言:
let、if let、while let、for循环和函数参数。 - let-else — Rust by Example(英文):
let ... else的简短可运行示例。 - if let — 通过例子学 Rust:什么时候
if let比完整的match更好读。