Skip to content

附录 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

⚠️ 陷阱:网上搜索错误码时,务必对照 rustc 1.98.1 / edition 2024 的行为。 同一个码在不同版本可能换了措辞或换了触发条件(例如 E0301 现在已不再由编译器发出)。


B.1 错误码速查主表

「修法要点」只写最短路径;每条都对应 B.2 中更详细的症状分组。

错误码一句话含义常见触发写法修法要点详见章节
E0004match 未覆盖所有取值match x { 1 => {} }补全分支或加 _ => {};枚举加了变体就要回来改变量与流程控制
E0005模式里的常量无法当模式匹配const A: i32 = 1; match 1 { A => {} }用字面量或改 if 判断;模式中不能用非结构化的常量表达式变量与流程控制
E0007绑定名重复(同一模式内绑定两次)match (1, 2) { (x, x) => {} }两个位置用不同名字,或第二个用 _变量与流程控制
E0015const 上下文调用了非 const fnconst 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 操作却没有 unsafeunsafe fn f(p: *const i32) { *p }(2024 edition)把不安全操作包进 unsafe { ... }测试与性能
E0178类型里 + 歧义fn f(x: &dyn Send + Sync) {}加括号明确边界:&(dyn Send + Sync)结构体与 trait
E0183手动实现 Fn/FnMut/FnOnceimpl 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(...) 转换元素类型,或写显式类型标注引导推断集合与迭代器
E0277trait 约束不满足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,或改用带生命周期的参数化类型泛型与生命周期
E0317if 被当作表达式但没有 elselet 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变量与流程控制
E0432use 导入的路径不存在use std::vec::vec::Vec;用 IDE 补全或翻文档确认路径;Vecstd::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(正文不推荐)环境搭建与工具链
E0562impl 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 里未标 pubfn/struct逐级加 pub,或用 pub(crate) 精确放开工程化与常用 crate
E0609类型没有这个字段p.z,但 P 只有 x/y检查字段名;元组结构体要用 .0 访问结构体与 trait
E0616字段不可访问(私有字段)跨模块读 p.secretpub 或提供 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();先把临时值绑定到变量,再取引用所有权
E0728async 之外使用了 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
E0774derive 用在了非 struct/enum/union 上#[derive(Clone)] type Bar;(在 trait 里)#[derive] 移到结构体/枚举定义上结构体与 trait
E0782trait 对象必须写 dynfn 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 herevalue 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 方法(如 parsetry_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)] 一起写上;注意 DropCopy 互斥, 含 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 safelySend 不满足)

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();
}
  • 处方:换成 ArcRc<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 个习惯

  1. 先修第一条,不看最后一条。rustc 的错误有连锁反应:E0433 修好后,后面一串 E0425(找不到名字)常会一起消失。按行号从小到大处理。
  2. 先看 help:,再看 note:,最后才自己推理help 是编译器给的可执行修法, 命中率极高;note 解释机制;只有两者都不适用时才回到借用规则自己推。
  3. expected / found 两行对齐看。类型类错误 90% 的答案就在这两行: 谁期望什么、实际给了什么。常见差异是「多/少了一个 &」、「&T vs T」、 「String vs &str」。
  4. 找「另一半」。带 -- 的次标签指向冲突的一方(immutable borrow occurs here), 它比主标签更有信息量:主标签只说「你错在这」,次标签说「因为那里」。
  5. 先猜错误码,再决定改法。看到 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 satisfiedtrait 约束未满足加约束或给类型实现 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)速查

警告不影响编译,但往往预示真实 bugcargo fix 能自动修掉其中一部分。

警告含义怎么处理
unused variable变量声明后从未使用改名成 _x 表示有意忽略;或删掉该绑定
unused importuse 进来的名字没用到删掉该 use;避免用 use ...::* 掩盖问题
dead_code私有条目从未被引用删掉、加 #[allow(dead_code)],或改成 pub(若确实是 API)
unused_must_use忽略了标了 #[must_use] 的返回值Result 一定要处理:?expectif let Err(e);不要用 let _ = 掩盖
unreachable_patterns某个模式永远匹配不到删掉重复分支,或把特殊分支提到前面
non_snake_case变量/函数名不是 snake_case函数与变量用小写下划线;类型用 CamelCase(后者对应 non_camel_case_types
unused_mut声明了 mut 但从未修改去掉 mut;这也说明借用设计可以更严格
deprecated使用了已废弃的 APInote: 指出的替代品迁移
unused_parens / unused_braces多余的括号/花括号删掉;或检查是否漏了关键字
unused_unsafeunsafe 块里没有任何不安全操作删掉 unsafe 块;这说明该块可以变成安全代码
field is never read结构体字段从未被读取删掉字段,或加 #[allow(dead_code)] 并注明原因
value assigned to ... is never read赋值后从未读取检查是否漏用了变量,或该计算是多余的
unused_comparisons比较结果恒为真/假检查数据类型的取值范围与有符号性
irrefutable_let_patternsif let/while let 的模式必然匹配改用 let 或直接写语句
dropping_copy_typesCopy 类型调用 std::mem::drop该调用什么也不做,直接删掉
dropping_references对引用调用 std::mem::drop只有引用本身被丢弃,要释放锁请传所有权
for_loops_over_falliblesOption/Resultfor 循环改用 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 只是警告)。 正确做法:改用 AtomicUsizeMutex<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 的新旧名。
  • 部分标准库函数变为 unsafestd::env::set_varstd::env::remove_var 在 2024 edition 起 调用需要 unsafe 块。迁移期 deprecated_safe_2024 lint 会在旧 edition 上提示这一点。
  • extern 块要写 unsafe extern:2024 edition 起 extern "C" { ... } 需要 写成 unsafe extern "C" { ... };旧 edition 上 missing_unsafe_on_extern lint 会给出提示。

⚠️ 注意: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

延伸阅读

内容以 rustc 1.98.1 · Rust 2024 edition 为基准