17-征服编译错误

《Rust语言圣经》「征服编译错误」精要速成

精要笔记 · 基于 Rust语言圣经「征服编译错误」

征服编译错误

两类内容:对抗编译检查(理解报错、改设计)与 Rust 常见陷阱(运行时/语义坑)。

对抗编译检查

生命周期

生命周期过大-01

  • 现象:list.get_interface().noop() 后仍无法 use_list(&list) — cannot borrow as immutable ... borrowed as mutable
  • 根因:get_interface(&'a mut self) 的 'a 与 List<'a> 同寿 → 借用活到 main 结束
  • 修复:引入更短生命周期 'b:fn get_interface<'b>(&'b mut self) -> Interface<'b, 'a> where 'a: 'b
  • 要点:方法签名生命周期≠「这行代码结束就释放」;返回含引用时绑定到参数生命周期

生命周期过大-02

  • 现象:结构体字段间相互引用 + 统一 'a 导致无法拆分借用
  • 根因:一个大 'a 覆盖所有字段引用,编译器认为交叉借用冲突
  • 修复:为不同字段/方法使用独立生命周期参数,约束 where 'long: 'short
  • 要点:「谁借谁、借多久」要分开标注,不要一个 'a 包打天下

循环中的生命周期

  • 现象:loop { arr.len(); let t = &mut arr[i]; return t; } — 双重借用 + 跨迭代 mutable borrow
  • 根因:编译器保守认为 tile 的 mut borrow 延续到整个 '1(含下次循环);arr.len() 与 &mut arr[i] 冲突
  • 尝试:去掉中间变量仍可能失败(返回引用绑定整个 loop)
  • 修复:
    • 引用外移:在 loop 外先完成索引选择逻辑
    • 用索引而非返回 &mut 元素引用
    • 重构为不跨 loop 返回 borrow
  • 深层:非线性控制流 + NLL 仍保守;未来 Polonius 借用检查器更精确
  • 要点:loop 内返回引用 = 高危模式

闭包碰到特征对象-01

  • 现象:闭包存入 Box<dyn Fn()> / 线程,move || ... 捕获局部引用报错
  • 根因:
    • trait object 常要求 'static
    • 闭包捕获 &T → 闭包 lifetime 绑定到 T,非 'static
  • 四种情况:
    • 无捕获 → OK
    • 捕获值(move)→ OK
    • 捕获 &local → 非 'static,失败
    • 要捕获引用 → 让数据活得够久('static、Arc、泄漏)或改 API 带 lifetime
  • 修复:move 所有权进闭包;或 Box<dyn Fn() + 'a> 显式生命周期;或不用 trait object
  • 要点:'static 闭包 ≠ 静态变量;= 不含非 'static 引用捕获

重复借用

同时在函数内外使用引用

  • 正确:同一函数内 let a = &mut self.a; let b = &mut self.b; — 编译器分字段借用
  • 错误:let b = &mut self.b; self.increase_a(); — increase_a(&mut self) 视为整 struct mut borrow
  • 根因:跨函数时编译器无法证明只碰一个字段(非词法、保守)
  • 修复:
    • 内联字段操作,不通过 &mut self 方法
    • 用 RefCell/Cell 内部可变(运行时检查)
    • 提取字段到局部变量再操作
  • CPU 模拟例子:多寄存器模拟分字段 vs 整 struct 借用

智能指针引起的重复借用错误

  • 现象:RefCell 包 struct,分字段 borrow_mut 仍冲突
  • 根因:RefCell::borrow_mut 锁的是整个 RefCell,非单字段
  • 修复:每个需独立借用的字段单独 RefCell;或展开代码避免嵌套 borrow
  • 延伸:Mutex、RwLock 等同理 — 锁粒度是容器级
  • 练习:多 RefCell 字段的正确拆分模式

类型未限制(todo)

  • 现象:泛型方法中 T 未 impl 所需 trait,报错模糊或延迟到调用处
  • 要点:为 type parameter 加 where T: Trait;必要时 trait bound 写在 impl 块
  • 状态:原书 todo,遇此类错误先查缺失 trait bound

幽灵数据(todo)

  • 现象:结构体含 PhantomData 或裸指针,Send/Sync/variance 与直觉不符
  • 要点:PhantomData<T> 标记逻辑持有关系;*const T 默认 !Send
  • 状态:原书 todo;链表/unsafe 章节有 PhantomData 详例

Rust 常见陷阱

for 循环中使用外部数组

  • 现象:for i in 0..v.len() { v.push(...); } — 死循环或 panic
  • 根因:v.len() 每轮变;索引与长度不同步
  • 修复:for item in v.iter()、while i < v.len() 谨慎设计、或倒序/remove

线程类型导致的栈溢出

  • 现象:默认线程栈 2MB(平台相关),递归或大数组 on stack → stack overflow
  • 根因:std::thread 栈固定;深层递归 / [[u8; N]] on stack
  • 修复:放堆(Box/Vec)、减递归、thread::Builder::new().stack_size(...)

算术溢出导致的 panic

  • 现象:Debug 默认 overflow panic;Release 可能 wrapping
  • 根因:+ - * checked 行为随 profile 变
  • 修复:checked_add / saturating_* / wrapping_*;Release 仍注意 overflow-checks

闭包中奇怪的生命周期

  • 简单代码:闭包捕获引用,返回闭包,生命周期绑定输入
  • 复杂代码:HRTB、Fn vs FnMut、闭包返回闭包时 lifetime _elision 失效
  • 修复:显式标注 where for<'a> ... 或 'a on trait object;减少多层嵌套闭包

可变变量不可变?

  • 现象:let mut x = ...; let y = &x; x = ... — 有 immut borrow 时不能 mut reassign
  • 根因:mut 变量 ≠ 永远可变;存在活跃引用时禁止赋值(NLL)
  • 修复:缩小 y 作用域;先用完借用再赋值

可变借用失败引发的深入思考

  • 场景:重构后同一 scope 内 &mut self 方法 + 字段 mut ref 冲突(与「重复借用」同源)
  • 闭包例子:闭包捕获 &mut self 同时调用 self.foo()
  • 修复:分字段借用、内联、RefCell、重构 API 避免同时 &mut self 与字段 borrow
  • 要点:编译器「冤枉」你时,往往是无法跨函数证明 disjoint fields

不太勤快的迭代器

  • 现象:accounts.into_iter().map(|a| { map.insert... }); 输出 {}
  • 根因:迭代器适配器懒惰,map 不执行,无 consumer
  • 修复:.collect()、for 消费、for_each、.count() 等 consumer
  • 对比:for 循环会立刻执行

奇怪的序列 x..y

  • 现象:0..0 空、(0..5).len() 误用、.. 范围类型混淆
  • 要点:a..b 半开区间;..=b 含 b;数组索引注意 off-by-one

无处不在的迭代器

  • 现象:需要 Vec 却链式 adapter 类型对不上;或多次 consume iterator
  • 根因:adapter 返回新 Iterator;move 后不能再用
  • 修复:.collect::<Vec<_>>();需多次遍历则 .clone() on iter(有代价)或 collect 后 reuse
  • 最 Rusty:明确 consumer 终点

线程间传递消息导致主线程无法结束

  • 现象:mpsc channel 未 drop sender,主线程 recv 永久阻塞
  • 根因:Channel 存活则 receiver 等待
  • 修复:drop 多余 Sender;用 drop(tx) 显式;或 scoped thread / oneshot

警惕 UTF-8 引发的性能隐患

  • 现象:对 String 逐 chars() 与逐字节处理性能差 orders
  • 根因:UTF-8 变长;.chars() 解码有开销;ASCII 路径应用 bytes
  • 修复:确认纯 ASCII 用 as_bytes() / bytes();国际化才 chars()

排错速查

错误关键词优先怀疑
borrowed as mutable ... immutable生命周期过大 / loop 返回 ref / 重复借用
cannot borrow *self as mutable more than once分字段借用被函数调用打破
does not live long enough引用指向局部;闭包/trait object lifetime
expected closure ... Fn()捕获引用非 'static
iterator 无效果缺 consumer
stack overflow递归/大栈数组
channel blocksender 未 drop

源码路径:shengJing/_src/src/compiler/fight-with-compiler/ 与 pitfalls/。

最后修改 July 30, 2026: 更新 (33710aaae)