17-征服编译错误
《Rust语言圣经》「征服编译错误」精要速成
5 分钟阅读
精要笔记 · 基于 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
- trait object 常要求
- 四种情况:
- 无捕获 → 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、
FnvsFnMut、闭包返回闭包时 lifetime _elision 失效 - 修复:显式标注
where for<'a> ...或'aon 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 终点
线程间传递消息导致主线程无法结束
- 现象:
mpscchannel 未 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 block | sender 未 drop |
源码路径:shengJing/_src/src/compiler/fight-with-compiler/ 与 pitfalls/。