附录 B · 编译器报错速查
本附录是「报错字典」:先查错误码,再看症状分组,最后看关键词表。 术语译名以附录 A · 术语中英对照为准; 借用规则与所有权的原理见所有权与借用。
使用说明:怎么读 rustc 错误
一条 rustc 错误由固定的几块拼成,读懂每一块,定位速度会快一个数量级。
text
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> src/main.rs:5:5
| ^^^^^^^^^^^^^^^^^^^^
4 | let r = &v;
| -- immutable borrow occurs here
5 | v.push(2);
| ^^^^^^^^^ mutable borrow occurs here
6 | println!("{r:?}");
| - immutable borrow later used here各块的含义:
error[E0502]:错误级别 + 错误码。错误码是最重要的检索键,同一个码的修法基本固定。--> src/main.rs:5:5:位置,格式是文件:行:列。注意它常指向第二次借用,而不是首次借用。^^^波浪线:主标签(primary span),出问题的那段代码。let r = &v;下的--:次标签(secondary span),说明「冲突的另一半在哪」,这是最有价值的信息。= note::补充解释机制,例如immutable borrow later used here。help::编译器给出的可直接照做的修法,优先尝试它。- 结尾的
aborting due to N previous errors:本次编译失败;先修最上面那条,后面的常是连锁反应。
常用的查阅手段:
powershell
# 在项目里查错误码全文解释(推荐:版本与本地工具链一致)
cargo explain E0382
# 不依赖项目,直接问本地 rustc
rustc --explain E0502- 错误码总索引(官方):Rust error codes index
- 借用检查器与诊断格式(官方):The rustc book · 诊断与 lint
- 所有权教学主线(官方):Rust Book · 认识所有权
⚠️ 陷阱:网上搜索错误码时,务必对照 rustc 1.98.1 / edition 2024 的行为。 同一个码在不同版本可能换了措辞或换了触发条件(例如
E0301现在已不再由编译器发出)。
B.1 错误码速查主表
「修法要点」只写最短路径;每条都对应 B.2 中更详细的症状分组。
| 错误码 | 一句话含义 | 常见触发写法 | 修法要点 | 详见章节 |
|---|---|---|---|---|
| E0004 | match 未覆盖所有取值 | match x { 1 => {} } | 补全分支或加 _ => {};枚举加了变体就要回来改 | 变量与流程控制 |
| E0005 | 模式里的常量无法当模式匹配 | const A: i32 = 1; match 1 { A => {} } | 用字面量或改 if 判断;模式中不能用非结构化的常量表达式 | 变量与流程控制 |
| E0007 | 绑定名重复(同一模式内绑定两次) | match (1, 2) { (x, x) => {} } | 两个位置用不同名字,或第二个用 _ | 变量与流程控制 |
| E0015 | 在 const 上下文调用了非 const fn | const N: Option<u8> = f(); | 把被调函数标注为 const fn,或改为运行期计算 | 变量与流程控制 |
| E0027 | 结构体模式没有覆盖全部字段 | let Point { x } = p; | 补全字段或加 ..:let Point { x, .. } = p; | 结构体与 trait |
| E0046 | 没有实现 trait 的全部必需项 | impl T for S { fn a(&self) {} },而 trait 还声明了 b | 补上缺失的方法,或给 trait 方法写默认实现 | 结构体与 trait |
| E0053 | 实现的函数签名与 trait 声明不一致 | trait 写 fn f(&self, x: i32),impl 写 fn f(&self, x: i64) | 三处对齐:参数类型、&self/&mut self、返回类型 | 结构体与 trait |
| E0061 | 函数调用实参个数不对 | f(1),而 fn f(a: i32, b: i32) | 补齐参数,或给参数加 Option 包装 | 变量与流程控制 |
| E0063 | 结构体字面量缺少字段 | let p = P { x: 1 };,但 P 还有 y | 补齐字段,或加 ..Default::default() | 结构体与 trait |
| E0072 | 递归类型大小无限 | struct Node { next: Node } | 用 Box<Node>/Option<Box<Node>> 引入间接层 | 结构体与 trait |
| E0080 | 常量求值失败(编译期计算出错) | const A: [u8; 1 / 0] = [0]; | 检查除数、数组下标、溢出;常量求值出错就是编译错误 | 变量与流程控制 |
| E0106 | 缺少生命周期标注 | fn g() -> &str { "a" } | 给参数与返回类型加同一个 'a,或返回拥有所有权的类型 | 所有权 |
| E0107 | 泛型参数个数不对 | HashMap<String>(缺 V) | 补全类型参数;注意 Box<dyn Trait> 与 Box<T> 的写法差异 | 泛型与生命周期 |
| E0117 | 为外部类型实现外部 trait(违反孤儿规则) | impl From<u8> for u8 {} | 改成 newtype 包装(struct MyU8(u8);)再实现 | 结构体与 trait |
| E0119 | 实现冲突(同一类型有两份实现) | 手写 impl T for S,而 #[derive(T)] 已生成一份 | 删掉手写实现,或用 #[derive] 参数控制生成 | 结构体与 trait |
| E0121 | 条目签名里不能用 _ 占位类型 | fn foo() -> _ { 5 } | 把返回类型写具体;_ 只能用在函数体内的 let 上 | 变量与流程控制 |
| E0133 | 使用了 unsafe 操作却没有 unsafe 块 | unsafe fn f(p: *const i32) { *p }(2024 edition) | 把不安全操作包进 unsafe { ... } | 测试与性能 |
| E0178 | 类型里 + 歧义 | fn f(x: &dyn Send + Sync) {} | 加括号明确边界:&(dyn Send + Sync) | 结构体与 trait |
| E0183 | 手动实现 Fn/FnMut/FnOnce | impl FnOnce<()> for MyClosure {} | 改用闭包或自定义 trait;手动实现 Fn* 家族目前需要 nightly | 智能指针与闭包 |
| E0207 | 类型参数没有被 impl 约束住 | impl<T, U> S<T> { }(U 从未出现) | 删掉多余类型参数,或把它放进 S<...> / where 子句 | 泛型与生命周期 |
| E0220 | 使用了不存在的关联类型 | T::Output,但 T 的 trait 里没有该关联类型 | 确认 trait 定义里的关联类型名,改用正确名称 | 泛型与生命周期 |
| E0223 | 关联类型写法有歧义 | let x: Trait::X; | 用完全限定:<Struct as Trait>::X | 泛型与生命周期 |
| E0255 | 导入的名字与本地定义冲突 | use std::fmt; fn fmt() {} | 给其中一个改名:use std::fmt as std_fmt; | 工程化与常用 crate |
| E0271 | 关联类型不匹配 | 迭代器的 Item 与接收方的期望类型不一致 | 用 .map(...) 转换元素类型,或写显式类型标注引导推断 | 集合与迭代器 |
| E0277 | trait 约束不满足 | for x in 5 {}、对不支持的类型用 ?、T 未实现 Debug | 实现/派生该 trait,加错误转换,或换用满足约束的类型 | 泛型与生命周期 |
| E0301 | (历史)模式守卫里做可变借用 | Some(_) if o.take().is_none() | 编译器现在改报 E0510;把副作用挪出守卫 | 变量与流程控制 |
| E0308 | 类型不匹配(最常见的报错) | let x: i32 = "a";、if 两支类型不同 | 看 expected/found 两行逐一统一类型;常是漏了 & | 变量与流程控制 |
| E0310 | 泛型参数可能活得不够久('static 不满足) | fn f<T>(t: T) -> Box<dyn Any>(T 可能含借用) | 加 T: 'static,或改用带生命周期的参数化类型 | 泛型与生命周期 |
| E0317 | if 被当作表达式但没有 else | let x = if c { 1 }; | 补 else 分支,或把 if 当语句并在分支里赋值 | 变量与流程控制 |
| E0369 | 运算符没有对应实现 | 1u8 + 1u16、自定义类型用 == 但没实现 PartialEq | 统一类型;为自定义类型派生 PartialEq/PartialOrd | 结构体与 trait |
| E0373 | 闭包可能比当前函数活得久,但只借用了局部数据 | thread::spawn(|| println!("{v:?}")) | 加 move,或先 clone 出闭包要用的数据 | 智能指针与闭包 |
| E0382 | 使用了已被移动的值 | let t = s; println!("{s}");、循环里移动同一变量 | 改成借用、先 clone、或在循环内重新构造值 | 所有权 |
| E0384 | 对不可变变量二次赋值 | let x = 1; x = 2; | 声明时写 let mut x,或改用新变量遮蔽(shadowing) | 变量与流程控制 |
| E0405 | 使用了未定义的 trait | 拼写错误、缺少 use | 检查拼写并导入:use std::fmt::Display; | 结构体与 trait |
| E0412 | 使用了未定义的类型 | 类型名拼错、模块未导入、忘了定义 | 检查拼写与 use;确认类型确实已声明 | 结构体与 trait |
| E0425 | 使用了未定义的变量/函数 | println!("{}", nope) | 检查拼写与作用域;确认已 let 或已 use | 变量与流程控制 |
| E0432 | use 导入的路径不存在 | use std::vec::vec::Vec; | 用 IDE 补全或翻文档确认路径;Vec 在 std::vec::Vec | 工程化与常用 crate |
| E0433 | 路径前缀解析不到 crate/模块 | foo::bar(),但 foo 既不是模块也没导入 | 加 use,或用 crate::/super::/self:: 写清前缀 | 工程化与常用 crate |
| E0451 | 结构体字段是私有的 | 在别的模块里 p.secret = 1 | 把字段改成 pub,或提供 setter 方法 | 工程化与常用 crate |
| E0499 | 同时存在两个可变借用 | let a = &mut v; let b = &mut v; | 让两个借用先后结束(用花括号限作用域),或用索引、split_at_mut | 所有权 |
| E0502 | 可变借用与不可变借用冲突 | let r = &v; v.push(2); | 先结束不可变借用的使用,或先改完再取引用 | 所有权 |
| E0505 | 值被借走后又试图移动它 | let r = &s; let t = s; | 调换顺序:先 move 再借用,或先 clone | 所有权 |
| E0507 | 从借用的内容里移动出值 | let s = v[0];(Vec<String>) | 用 .clone()、&v[0],或用 std::mem::take 取走 | 所有权 |
| E0510 | 在模式守卫里做可变借用 | Some(_) if v.pop().is_some() | 守卫不能有副作用;把判断挪到分支体里 | 变量与流程控制 |
| E0515 | 返回了指向局部变量的引用 | fn f() -> &i32 { let x = 5; &x } | 返回拥有所有权的值,或让调用方传入缓冲区 | 所有权 |
| E0521 | 借用的数据逃出了闭包 | |el: &str| list.push(el)(闭包参数写了生命周期) | 去掉闭包参数的类型标注,让编译器推断 | 智能指针与闭包 |
| E0531 | 模式后缺少冒号或 => 写错 | match 分支的 =>、逗号写漏 | 检查 match 每个分支的 => 与逗号 | 变量与流程控制 |
| E0554 | 稳定版编译器不能使用 #![feature] | #![feature(never_type)] | 移除该属性,或改用 nightly(正文不推荐) | 环境搭建与工具链 |
| E0562 | impl Trait 用在了不允许的位置 | 嵌套 impl Trait,或写进关联类型位置 | 把约束改成泛型参数 + where,或为关联类型引入泛型参数 | 泛型与生命周期 |
| E0596 | 试图通过 & 获取可变借用 | let r = &x; let m = &mut *r; | 把上层引用改成 &mut,或使用内部可变性类型 | 所有权 |
| E0597 | 值活得不够久(借用超出被借用值的生命周期) | let r; { let x = 5; r = &x; } | 把被借用的值提到外层作用域,或让使用点提前 | 所有权 |
| E0599 | 找不到该方法 | 方法名拼错、trait 未导入、约束未满足 | 检查拼写、use 对应 trait、补 T: Trait 约束 | 结构体与 trait |
| E0603 | 条目是私有的(模块可见性) | 访问 mod 里未标 pub 的 fn/struct | 逐级加 pub,或用 pub(crate) 精确放开 | 工程化与常用 crate |
| E0609 | 类型没有这个字段 | p.z,但 P 只有 x/y | 检查字段名;元组结构体要用 .0 访问 | 结构体与 trait |
| E0616 | 字段不可访问(私有字段) | 跨模块读 p.secret | 加 pub 或提供 getter 方法 | 工程化与常用 crate |
| E0618 | 调用了不是函数的东西 | X::Entry()、let x = 0; x(); | 确认它是函数还是值;枚举变体要带数据才是构造调用 | 变量与流程控制 |
| E0621 | 签名里的生命周期与函数体的数据流不符 | fn f<'a>(x: &'a i32, y: &i32) -> &'a i32 { if x > y { x } else { y } } | 让 y 也用 'a,或改成只返回来自 x 的数据 | 所有权 |
| E0631 | 闭包参数类型与要求不符 | 把 |x: i32| 传给期待 |x: &i32| 的位置 | 让参数类型与目标签名一致,去掉多余标注 | 智能指针与闭包 |
| E0658 | 使用了未稳定的语言特性 | try { }、impl Trait 在 trait 中的旧用法 | 换成稳定写法;真要试验就切 nightly 并标 #![feature(...)] | 环境搭建与工具链 |
| E0689 | 在类型未定的数字上调用方法 | let x = 2.0; x.neg(); | 给字面量/变量写明类型:let x: f32 = 2.0; | 变量与流程控制 |
| E0716 | 临时值在借用期间被释放 | let s = String::from("a").as_str(); | 先把临时值绑定到变量,再取引用 | 所有权 |
| E0728 | 在 async 之外使用了 await | 普通 fn 里写 f().await | 改成 async fn,或在 async 块/#[tokio::main] 里写 | 异步编程 |
| E0746 | 返回值是不能有 trait 对象形式的不定长类型 | fn foo() -> dyn T { S } | 返回 impl T(单一类型)或 Box<dyn T>(多种类型) | 结构体与 trait |
| E0751 | 同一类型同时有正反两向 trait 实现 | impl T for i32 {} 与 impl !T for i32 {} | 删掉其中一个;negative impl 目前仍是实验特性 | 结构体与 trait |
| E0774 | derive 用在了非 struct/enum/union 上 | #[derive(Clone)] type Bar;(在 trait 里) | 把 #[derive] 移到结构体/枚举定义上 | 结构体与 trait |
| E0782 | trait 对象必须写 dyn | fn test(arg: Box<Foo>) {} | 写成 Box<dyn Foo>;2015/2018 edition 允许省略,2021 起是硬错误 | 结构体与 trait |
| E0793 | 对 #[repr(packed)] 结构体字段取了引用 | let r = &packed.field; | 改为按值复制,或 std::ptr::addr_of! + read_unaligned | 测试与性能 |
🧠 原理:E0499/E0502/E0505/E0507/E0597/E0716 这六个码占借用类报错的绝大多数。 记住一句话就能覆盖它们:同一时刻,一个值要么有任意多个只读引用,要么只有一个可写引用, 而且引用不能比被引用的值活得久。
B.2 按主题分组的「症状 → 诊断 → 处方」
B.2.1 所有权与移动相关
症状 1:error[E0382]: borrow of moved value: s
rust
// 触发:所有权已经换手,旧名字就作废了 —— 这是 Rust 防双重释放的手段
fn main() {
let s = String::from("hi");
let t = s; // move:所有权交给 t
println!("{s}"); // 编译失败 E0382
println!("{t}");
}- 诊断:报错里会同时标出
value moved here与value borrowed here after move两处。 - 处方:需要两份就显式
let t = s.clone();;只需要读就改成let t = &s;。
症状 2:error[E0507]: cannot move out of index of Vec<String>
rust
// 触发:索引语法 v[0] 只能借出,不能把元素搬走(否则原容器会被掏空)
fn main() {
let v = vec![String::from("a")];
let s = v[0]; // 编译失败 E0507
println!("{s}");
}- 诊断:如果元素类型是
Copy就不会报错,所以这条错误通常出现在String/Vec这类类型上。 - 处方:
v[0].clone()(真需要副本)、&v[0](只读)、或用v.into_iter()按值消费整个容器。
症状 3:error[E0505]: cannot move out of s because it is borrowed
rust
// 触发:先借后 move,借用可能变成悬垂引用
fn main() {
let s = String::from("a");
let r = &s; // 不可变借用开始
let t = s; // 编译失败 E0505:还想 move
println!("{r}");
println!("{t}");
}- 处方:交换两行(先 move 再借用);或让
r的作用域先结束;或let t = s.clone();。
症状 4:循环里移动同一个变量
rust
发:for 每次迭代都执行同一句 move,第一次之后就无值可移
fn main() {
let s = String::from("a");
for _ in 0..2 {
// 处方之一:每轮造一份新的,让循环体各自拥有
let s = s.clone();
println!("{}", consume(s));
}
}
fn consume(s: String) -> usize { s.len() }- 处方:循环内
clone、把s换成&s(若函数接受引用)、 或先把s放进Vec/迭代器统一消费。
B.2.2 借用冲突
症状 1:不可变与可变借用同时存在
rust
// 触发:r 还要在 push 之后使用,所以它的借用期覆盖了 push
fn main() {
let mut v = vec![1];
let r = &v;
v.push(2); // 编译失败 E0502
println!("{r:?}");
}- 处方:把
println!("{r:?}")提到push之前,让r的借用提前结束。
症状 2:同一作用域内两次可变借用
rust
// 触发:编译器不允许两个 &mut 同时存在(否则会出现两个可写别名)
fn main() {
let mut v = vec![1];
let a = &mut v;
let b = &mut v; // 编译失败 E0499
a.push(1);
b.push(2);
}- 处方:用花括号把两次借用分开;或一次性
v.push(1); v.push(2);;切片场景用split_at_mut。
症状 3:迭代时修改集合
rust
发:迭代器内部持有 &v,而 push 需要 &mut v
fn main() {
let mut v = vec![1, 2, 3];
for x in &v {
// v.push(*x); // 若取消注释就会编译失败 E0502
println!("{x}");
}
// 处方:先把要新增的元素收集起来,循环结束后再统一追加
let extra: Vec<i32> = v.iter().map(|x| x * 10).collect();
v.extend(extra);
println!("{v:?}");
}- 处方:先收集要新增的元素,循环结束后再统一
extend;或改用索引循环for i in 0..v.len()。
症状 4:error[E0596]: cannot borrow *r as mutable, as it is behind a & reference
rust
// 触发:只读引用无法"升级"为可写引用 —— 否则 &T 的只读承诺就毫无意义
fn main() {
let x = 1;
let r = &x;
let m = &mut *r; // 编译失败 E0596
*m = 2;
}- 处方:把源头改成
let mut x,并取&mut x;或改用Cell/RefCell做内部可变性。
B.2.3 生命周期
症状 1:error[E0597]: x does not live long enough
rust
// 触发:被借用的值在引用仍被使用时先一步离开作用域
fn main() {
let r;
{
let x = 5;
r = &x; // 编译失败 E0597:x 在这个块结束时被 drop
}
println!("{r}");
}- 处方:把
let x = 5;提到外层,让x至少和r一样长。
症状 2:error[E0106]: missing lifetime specifier
rust
// 触发:多个输入引用 + 一个输出引用时,编译器无法猜出输出该借谁
fn longest(a: &str, b: &str) -> &str { // 编译失败 E0106
if a.len() > b.len() { a } else { b }
}
fn main() {
println!("{}", longest("aa", "b"));
}- 处方:写
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str,或返回拥有所有权的String。
症状 3:error[E0716]: temporary value dropped while borrowed
rust
// 触发:String 是临时的,语句结束就被释放,as_str() 拿到的引用立刻悬垂
fn main() {
let s = String::from("hello").as_str(); // 编译失败 E0716
println!("{s}");
}- 处方:先把拥有者绑到变量:
let owned = String::from("hello"); let s = owned.as_str();
症状 4:返回局部变量的引用
rust
// 触发:局部变量在函数返回时被 drop,引用不可能有效
fn f() -> &'static i32 {
let x = 5;
&x // 编译失败 E0515:cannot return reference to local variable `x`
}
fn main() { println!("{}", f()); }- 处方:返回拥有所有权的值(
i32);要返回大数据就返回String/Vec<T>。
症状 5:error[E0621]: explicit lifetime required in the type of y
rust
// 触发:签名承诺"只从 x 借",函数体却可能返回 y 的数据
fn foo<'a>(x: &'a i32, y: &i32) -> &'a i32 { // 编译失败 E0621
if x > y { x } else { y }
}
fn main() { println!("{}", foo(&1, &2)); }- 处方:把
y也标成&'a i32,让签名如实反映数据流。
B.2.4 trait 与泛型
症状 1:error[E0277]: the trait bound ... is not satisfied
rust
// 触发:泛型函数要求 Debug,但 T 上没有任何约束
fn show<T>(v: T) { println!("{v:?}"); } // 编译失败 E0277:T 没有 Debug 约束
fn main() { show(3); }- 处方:两种改法二选一 —— 把签名改成
fn show<T: std::fmt::Debug>(v: T), 或给类型加#[derive(Debug)];报错末尾的help:常直接给出consider further restricting。
症状 2:error[E0599]: no method named ... found
rust
// 触发:方法名拼错,或提供该方法的 trait 不在作用域内
fn main() {
let x = 5;
x.pus(1); // 编译失败 E0599
}- 处方:改对拼写;若是 trait 方法(如
parse、try_into),补use对应 trait。
症状 3:error[E0117]: only traits defined in the current crate can be implemented for primitive types
rust
// 触发:孤儿规则 —— 不能为外部类型实现外部 trait
impl From<u8> for u8 { fn from(v: u8) -> u8 { v } } // 编译失败 E0117
fn main() {}- 处方:用 newtype 包一层:
struct MyU8(u8);,再impl From<u8> for MyU8。
症状 4:error[E0046]: not all trait items implemented, missing: b
rust
// 触发:trait 声明了两个必需方法,impl 只写了一个
trait T { fn a(&self); fn b(&self); }
struct S;
impl T for S { fn a(&self) {} } // 编译失败 E0046:缺少 `b`
fn main() {}- 处方:补上
fn b(&self) {},或给 trait 里的b写默认实现。
症状 5:类型未实现 Clone / Debug
rust
// 触发:把未实现 Clone 的类型交给了要求 Clone 的位置
#[derive(Debug)]
struct Cfg { name: String }
fn dup<T: Clone>(v: &T) -> T { v.clone() }
fn main() {
let c = Cfg { name: String::from("a") };
let d = dup(&c); // 编译失败 E0277:Cfg 没有实现 Clone
println!("{c:?}{d:?}");
}- 处方:把
#[derive(Debug, Clone)]一起写上;注意Drop与Copy互斥, 含Drop字段的类型只能Clone不能Copy。
B.2.5 类型不匹配
症状 1:if 两个分支类型不同
rust
// 触发:if 是表达式,两条分支必须产出同一种类型
fn main() {
let x: i32 = if true { 1 } else { "a" }; // 编译失败 E0308
println!("{x}");
}- 处方:统一成同一类型(
else { 0 });确实要装不同类型就改用enum承载。
症状 2:忘了分号导致返回值类型变了
rust
发:末行多写一个分号 ⇒ 该表达式的值被丢弃,函数返回 ()
fn f() -> i32 {
let x = 1 + 1; // 正确写法:用 let 接住值,末行再返回
x
}
fn main() { println!("{}", f()); }- 处方:想让表达式成为返回值就不要加分号;加了分号就成了语句,值会变成
()并触发E0308。
症状 3:整数类型推断失败
rust
发:字面量的类型完全没有约束时,编译器不会瞎猜
fn main() {
// 处方:给字面量/变量写明类型,浮点方法调用才有着落
let y: f64 = 2.0;
println!("{}", -y);
}- 处方:写后缀(
2.0_f64)或加类型标注(let y: f64 = 2.0;),否则会得到E0689。 相关:E0282 type annotations needed也常由「collect()不知道收成什么容器」引起, 写.collect::<Vec<_>>()即可。
症状 4:? 用在了返回类型不兼容的函数里
rust
// 触发:? 会把错误提前返回给调用方,函数必须返回 Result/Option
use std::num::ParseIntError;
fn parse(s: &str) -> i32 {
let n: i32 = s.parse()?; // 编译失败 E0277:`?` 不能用在返回 i32 的函数里
n
}
fn main() { println!("{}", parse("1")); }- 处方:把返回类型改成
Result<i32, ParseIntError>;main里用?时把main改成fn main() -> Result<(), ParseIntError>。
B.2.6 闭包与线程
症状 1:error[E0373]: closure may outlive the current function
rust
// 触发:新线程不知道什么时候结束,闭包不能借用当前函数的栈变量
fn main() {
let v = vec![1, 2, 3];
std::thread::spawn(|| println!("{v:?}")); // 编译失败 E0373
}- 处方:改成
move ||,把v的所有权交给新线程;之后本线程就不能再用v了。
症状 2:error[E0277]: ... cannot be sent between threads safely(Send 不满足)
rust
发:Rc 的引用计数不是原子的,跨线程共享会破坏计数
use std::rc::Rc;
fn main() {
let p = Rc::new(1);
// 处方:把 Rc 换成 Arc,就能安全地 move 进新线程
let p = std::sync::Arc::new(*p);
std::thread::spawn(move || println!("{p}")).join().unwrap();
}- 处方:换成
Arc;Rc<RefCell<T>>换成Arc<Mutex<T>>。报错里的help: within ... the trait Send is not implemented for Rc<i32>会直接点出罪魁字段。
症状 3:error[E0521]: borrowed data escapes outside of closure
rust
// 触发:给闭包参数写了带生命周期的类型标注,等于凭空声明了一个新生命周期
fn main() {
let mut list: Vec<&str> = Vec::new();
let _add = |el: &str| { list.push(el); }; // 编译失败 E0521
}- 处方:去掉
: &str标注,写成|el| { list.push(el); },让编译器推断; 同时也要面对真实设计问题:list存的引用必须比闭包活得久。
症状 4:cannot borrow ... as mutable, as it is not declared as mutable
rust
发:FnMut 闭包调用时需要可变地访问自身,所以闭包变量本身要 mut
fn main() {
let mut count = 0;
// 处方:闭包变量声明为 mut;闭包按引用捕获,所以 count 之后仍可读取
let mut f = || { count += 1; count };
println!("{}", f());
println!("{count}");
}- 处方:
let mut f = || ...;;若闭包被 move 进别的结构体,把那个结构体的方法接收者改成&mut self。
B.3 读错误的 5 个习惯
- 先修第一条,不看最后一条。rustc 的错误有连锁反应:
E0433修好后,后面一串E0425(找不到名字)常会一起消失。按行号从小到大处理。 - 先看
help:,再看note:,最后才自己推理。help是编译器给的可执行修法, 命中率极高;note解释机制;只有两者都不适用时才回到借用规则自己推。 - 把
expected/found两行对齐看。类型类错误 90% 的答案就在这两行: 谁期望什么、实际给了什么。常见差异是「多/少了一个&」、「&TvsT」、 「Stringvs&str」。 - 找「另一半」。带
--的次标签指向冲突的一方(immutable borrow occurs here), 它比主标签更有信息量:主标签只说「你错在这」,次标签说「因为那里」。 - 先猜错误码,再决定改法。看到
E0502就知道是「读借用在后面还被用了」, 处理方式是缩短借用而不是clone。判断正确的改法,比快速消掉报错更重要。
⚠️ 陷阱:不要用
.clone()和无脑加mut来「消错」。 借用检查器报错通常说明数据的所有权设计有问题,clone只是把问题推迟到性能或逻辑层面。
B.4 错误信息里的关键词中英对照表
| 英文关键词 | 中文含义 | 读到它时该想什么 |
|---|---|---|
expected | 期望的类型 | 编译器要求的类型,写在这行之后 |
found | 实际得到的类型 | 你真实给的类型,与 expected 对比找差异 |
mismatched types | 类型不匹配 | 看 expected/found,通常差一个 & 或一次解引用 |
borrowed | 被借用 | 有活跃引用存在,注意它的作用域范围 |
borrowed here | 在此处发生借用 | 次标签位置,是冲突的源头之一 |
borrow later used here | 借用在之后仍被使用 | 决定借用何时结束的关键:把这次使用提前或缩短 |
moved | 已被移动 | 所有权已换手,旧名字失效 |
value moved here | 值在此处被移动 | move 的发生点 |
borrow of moved value | 借用了已移动的值 | 典型 E0382:先 move 后借 |
cannot move out of | 无法从中移出 | 只能借不能搬,常见于索引/解引用位置 |
dropped | 被释放 | 值的生存期结束 |
temporary value dropped while borrowed | 临时值在借用期间被释放 | 把临时值绑到 let 变量上 |
does not live long enough | 活得不够久 | 被借用的值先离开了作用域 |
lifetime | 生命周期 | 引用有效期的标注与约束 |
missing lifetime specifier | 缺少生命周期标注 | 多个输入引用时需显式写 'a |
explicit lifetime required | 需要显式生命周期 | 签名与函数体的数据流不一致 |
lifetime may not live long enough | 生命周期可能不够长 | 通常是 'static 约束不满足 |
unsized | 不定长(大小未知) | 该类型不能按值存放,需要 &/Box/Arc |
Sized | 大小已知 | 泛型默认隐含 T: Sized,要放宽得写 ?Sized |
coherence | 一致性 | 出现了冲突的实现,检查孤儿规则与 blanket impl |
conflicting implementations | 实现冲突 | 同一类型有两份 trait 实现(E0119) |
orphan rule | 孤儿规则 | trait 与类型必须有一个属于本 crate |
ambiguous | 有歧义 | 多个候选(类型、方法、名称)无法确定,需要显式限定 |
type annotations needed | 需要类型标注 | 推断不出来(collect、数字字面量、Default) |
cannot infer type | 无法推断类型 | 同上,补 ::<Vec<_>>() 或变量标注 |
trait bound ... is not satisfied | trait 约束未满足 | 加约束或给类型实现 trait(E0277) |
the trait ... is not implemented for | 该类型没有实现此 trait | 报错正文点出的类型就是要处理的类型 |
no method named | 找不到该方法 | 检查拼写与 trait 是否 use 进来(E0599) |
not all trait items implemented | 没有实现全部必需项 | 补方法或加默认实现(E0046) |
method not found in | 该方法在此类型上不存在 | 看是不是把 &self 写成了 self |
private / private field | 私有(字段/条目) | 加 pub 或提供访问方法(E0451/E0603/E0616) |
unreachable pattern | 不可达的模式 | 前面的分支已覆盖,删掉重复分支 |
non-exhaustive patterns | 模式不穷尽 | match 漏了取值,补分支或 _(E0004) |
cannot find | 找不到(名字) | 拼写、作用域、use 三选一排查(E0425/E0433) |
unresolved import | 导入无法解析 | 路径写错或模块未公开(E0432) |
raw pointer | 裸指针 | 解引用需要 unsafe(E0133) |
unsafe | 不安全操作 | 要么加 unsafe 块,要么改写为安全代码 |
uninitialized | 未初始化 | 变量声明后未赋值就使用 |
immutable | 不可变 | 需要写入就得声明 mut |
cannot assign | 不能赋值 | 目标是不可变绑定或不可变引用(E0384/E0594/E0596) |
may outlive | 可能活得更久 | 闭包/线程可能超出当前函数,考虑 move(E0373) |
escapes | 逃逸 | 借用或数据跑出了允许的范围(E0521) |
cannot be sent between threads safely | 不能安全地跨线程传递 | Send 不满足,检查字段里的 Rc/裸指针 |
cannot be shared between threads safely | 不能安全地跨线程共享 | Sync 不满足,如 Cell/RefCell |
dropped here while still borrowed | 在仍被借用时释放 | 明确 drop 位置与借用结束位置的先后 |
consider / help: | 编译器建议 | 优先尝试,通常就是标准修法 |
note: | 补充说明 | 解释为什么这样报错,帮助理解机制 |
aborting due to N previous errors | 因前面 N 个错误中止 | 先修最上面那条,其余多半是连锁反应 |
B.5 常见警告(warnings)速查
警告不影响编译,但往往预示真实 bug。cargo fix 能自动修掉其中一部分。
| 警告 | 含义 | 怎么处理 |
|---|---|---|
unused variable | 变量声明后从未使用 | 改名成 _x 表示有意忽略;或删掉该绑定 |
unused import | use 进来的名字没用到 | 删掉该 use;避免用 use ...::* 掩盖问题 |
dead_code | 私有条目从未被引用 | 删掉、加 #[allow(dead_code)],或改成 pub(若确实是 API) |
unused_must_use | 忽略了标了 #[must_use] 的返回值 | Result 一定要处理:?、expect、if let Err(e);不要用 let _ = 掩盖 |
unreachable_patterns | 某个模式永远匹配不到 | 删掉重复分支,或把特殊分支提到前面 |
non_snake_case | 变量/函数名不是 snake_case | 函数与变量用小写下划线;类型用 CamelCase(后者对应 non_camel_case_types) |
unused_mut | 声明了 mut 但从未修改 | 去掉 mut;这也说明借用设计可以更严格 |
deprecated | 使用了已废弃的 API | 按 note: 指出的替代品迁移 |
unused_parens / unused_braces | 多余的括号/花括号 | 删掉;或检查是否漏了关键字 |
unused_unsafe | unsafe 块里没有任何不安全操作 | 删掉 unsafe 块;这说明该块可以变成安全代码 |
field is never read | 结构体字段从未被读取 | 删掉字段,或加 #[allow(dead_code)] 并注明原因 |
value assigned to ... is never read | 赋值后从未读取 | 检查是否漏用了变量,或该计算是多余的 |
unused_comparisons | 比较结果恒为真/假 | 检查数据类型的取值范围与有符号性 |
irrefutable_let_patterns | if let/while let 的模式必然匹配 | 改用 let 或直接写语句 |
dropping_copy_types | 对 Copy 类型调用 std::mem::drop | 该调用什么也不做,直接删掉 |
dropping_references | 对引用调用 std::mem::drop | 只有引用本身被丢弃,要释放锁请传所有权 |
for_loops_over_fallibles | 对 Option/Result 用 for 循环 | 改用 if let/while let,意图更清楚 |
edition 里的 lint 级别变化
unsafe_op_in_unsafe_fn:从「不报」改为「默认 warn」。 在unsafe fn内部调用不安全操作,必须自己写unsafe { ... }。 动机:unsafe fn原本同时承担「调用方需负责」与「函数体内可做不安全操作」两个角色, 后者风险过高,所以拆开。迁移用cargo fix --edition。 (见 Edition Guide · unsafe_op_in_unsafe_fn)static_mut_refs:从「warn」升级为「默认 deny」。 对static mut取&/&mut在 2024 edition 是错误(2021 edition 只是警告)。 正确做法:改用AtomicUsize、Mutex<T>、OnceLock/LazyLock; 确实需要指针时用&raw const/&raw mut。 (见 Edition Guide · static mut 引用)elided_lifetimes_in_paths:没有变化,仍是 allow-by-default。 它只检查「隐藏的生命周期参数」(写&Foo而不是&Foo<'_>), 不会因为切到 2024 edition 就变成警告;需要显式打开:#![warn(elided_lifetimes_in_paths)]。 注意它与ELIDED_LIFETIME_IN_PATH是同一个 lint 的新旧名。- 部分标准库函数变为
unsafe:std::env::set_var、std::env::remove_var在 2024 edition 起 调用需要unsafe块。迁移期deprecated_safe_2024lint 会在旧 edition 上提示这一点。 extern块要写unsafe extern:2024 edition 起extern "C" { ... }需要 写成unsafe extern "C" { ... };旧 edition 上missing_unsafe_on_externlint 会给出提示。
⚠️ 注意:lint 的默认级别可能随版本调整。判断某个警告在你本地的确切级别, 最可靠的方式是看编译器输出末尾的
= note: #[warn(...)] on by default一行, 或查询 rustc lint 列表(官方)。
把警告当成错误
toml
# Cargo.toml 片段:让 CI 上任何警告都直接失败,避免警告堆积成噪音
[lints.rust]
unsafe_op_in_unsafe_fn = "deny"
[lints.clippy]
all = "warn"powershell
# 或临时在命令行上把警告提升为错误
$env:RUSTFLAGS = "-D warnings"; cargo build延伸阅读
- Rust error codes index:官方, 全部错误码索引,本附录主表的权威来源。
- The rustc book:官方, 命令行参数、lint 列表、诊断格式说明。
- rustc lint 列表(warn-by-default):官方, 查某个警告是否为默认开启。
- The Rust Edition Guide · Rust 2024:官方, 从 2021 迁移到 2024 时的行为变化清单。
- Rust Book · 认识所有权:官方, 借用检查器的教学入口。
- Rust 语言圣经(中文)· 借用检查:非官方, 中文讲解较友好,术语译法与 附录 A 可能不同。