Rustonomicon


There never was a good knife made of bad steel.

— Benjamin Franklin


photo by Ales Krivec(https://unsplash.com/@aleskrivec?utm_source=templater_proxy&utm_medium=referral) on Unsplash

总览

标题 主题
1 Meet Safe and Unsafe unsafe 语义、UB 清单、健全性与模块化
2 Data Layout repr 家族、对齐与大小、DST/ZST/空类型
3 Ownership 引用、别名、生命周期、variance、dropck、PhantomData
4 Type Conversions 强制转换、点操作符、as、transmute
5 Uninitialized Memory 初始化分析、drop 标志、MaybeUninit
6 OBRM (RAII) 构造/析构约定、递归 drop、泄漏与 mem::forget
7 Unwinding 栈展开、两级异常安全、中毒
8 Concurrency 数据竞争、Send/Sync、原子与内存顺序
9 Implementing Vec 稳定 Rust 从零手写 Vec
10 Implementing Arc 手写 Arc:引用计数与内存顺序
11 FFI extern、导出符号、repr(C)、C-unwind
12 Beneath std no_std 与 panic_handler

章节

第 1 章 Meet Safe and Unsafe(认识安全与不安全)

1.0 Safe 与 Unsafe 的二元结构

Rust = Safe Rust(真正的 Rust,绝不产生 UB)+ Unsafe Rust(额外允许几件危险的事)。

1
2
3
4
5
6
7
// Safe:标准库足以写出高性能程序
let v = vec![1, 2, 3];
let s: &str = "hello";

// 需要 Unsafe 的典型场景
// ① 调用 C 库 ② 实现标准库未暴露的底层抽象
// ③ 自己写标准库 ④ 直接折腾比特(类型系统不理解的操作)

1.1 Safe 与 Unsafe 如何交互

unsafe 关键字的两种用途:① 声明编译器无法检查的契约存在;② 声明程序员已确认契约被遵守。

1
2
3
4
5
6
7
// 用法一:声明契约存在(责任在对方)
unsafe fn get(idx: usize) -> i32; // 调用者必须保证 idx 合法
unsafe trait SafeToUse; // 实现者必须保证实现合规

// 用法二:声明契约已验证(责任在自己)
unsafe { v.get_unchecked(0) } // 我已检查 0 在界内
unsafe impl Send for MyType {} // 我论证过 MyType 跨线程安全

健全性 (soundness)——Safe Rust 的唯一根本性质:无论怎样,Safe Rust 都不可能导致 UB。由此产生不对称的信任关系

graph TD
    A["Safe 代码"] -->|"无条件信任"| B["Unsafe 代码"]
    B -->|"不能信任"| C["泛型 Safe 代码<br/>用户传入的 Ord 或闭包"]
    B -->|"可以信任"| D["具体实现:整数、切片<br/>风险可衡量"]
    C -. 泛型信任的解法 .-> E["unsafe trait<br/>责任转移给实现者"]
1
2
3
// 为什么 BTreeMap 不信任用户的 Ord:SloppyOrd 是纯 Safe 代码,却能让内部 unsafe 崩坏
impl Ord for Sloppy { fn cmp(&self, _) -> Ordering { Ordering::Less } } // 永远返回 Less
// BTreeMap 必须防御:行为错乱可以,UB 不行

1.2 Unsafe 能做什么(UB 清单)

Unsafe 额外能做的只有五件事:解引用裸指针、调用 unsafe 函数、实现 unsafe trait、访问可变 static、访问 union 字段。

1
2
3
4
5
6
7
8
9
10
// 语言核心 UB 清单(违反任一 = 编译器可任意处置你的程序)
let p: *const i32 = std::ptr::null();
unsafe { *p }; // ❌ UB:解引用 null(悬垂/未对齐同理)
unsafe { std::mem::transmute::<u8, bool>(3) }; // ❌ UB:产生非法 bool(不用也是 UB)
unsafe { *(0x7fffffffffffffff as *const u8) }; // ❌ UB:超出分配范围(GEP 界内承诺被破坏)

// 反例:这些是"安全"的(不构成 UB,但程序可能仍然是错的)
loop {} // ✅ 安全:死循环(内存泄漏同理)
deadlock(); // ✅ 安全:死锁
a + b // ✅ 安全(debug 下 panic,release 下回绕)

记忆口诀:"安全"≠"正确"——Rust 只承诺不 UB,不承诺程序逻辑正确喵。

1.3 与 Unsafe 共事:健全性是非局部的

健全性依赖非局部状态——纯 Safe 代码的一处改动就能摧毁 unsafe 的契约:

1
2
3
4
5
6
7
8
9
10
11
12
fn index(idx: usize, arr: &[u8]) -> Option<u8> {
if idx < arr.len() { // ✅ 健全:边界检查在前
// if idx <= arr.len() { // ❌ 一字符改动 → unsound → UB
unsafe { Some(*arr.get_unchecked(idx)) }
} else { None }
}

// 更隐蔽:不变式被 Safe 代码摧毁
impl<T> Vec<T> {
fn make_room(&mut self) { self.cap += 1; } // ❌ 100% Safe 却 unsound:
// cap 必须真实反映已分配容量 → ptr::write 写到未分配内存
}

结论:unsafe 污染的最小可靠边界是模块——用私有性把能触碰不变式的代码关在模块内:

1
2
3
4
mod my_vec {           // cap 是私有字段 → make_room 无法在模块外定义
pub struct Vec<T> { ptr: NonNull<T>, cap: usize, len: usize } // 私有 ✅
}
// 模块外只能通过 pub 方法(push/pop...)访问 → 不变式安全 → 安全抽象成立

第 2 章 Data Layout(数据布局)

2.1 repr(Rust):默认布局

  • 对齐 (alignment):值为 n 的类型只能放在 n 的倍数地址;总是 2 的幂
  • 关键不变量:大小必须是对齐的倍数(保证数组可按 size 步进索引)
1
2
3
4
struct A { a: u8, b: u32, c: u16 }   // 对齐 = max(1,4,2) = 4
// 概念布局(伪代码,编译器自由决定):
// struct A { a: u8, _pad1: [u8;3], b: u32, c: u16, _pad2: [u8;2] }
// ↑凑 b 的对齐 ↑凑总大小是 4 的倍数 → 12 字节

默认布局是刻意不指定的——只保证同一类型布局一致,不同类型无任何保证:

1
2
3
4
5
6
7
8
9
10
11
12
// ❌ 不要假设字段相同的两个类型布局相同
struct A { x: u8, y: u32 }
struct B { x: u8, y: u32 }
// transmute::<A, B>(a) // ❌ UB:字段顺序/填充可能不同

// 原因:泛型单态化需要不同字段排序消除填充
Foo<u16, u32> // 最优布局:u16 在前
Foo<u32, u16> // 最优布局:u32 在前(各自排列)

// 原因:空指针优化——tag 直接藏进指针的"非法值"里
size_of::<Option<&T>>() == size_of::<&T>() // ✅ None = null,无需额外判别式
size_of::<Option<Box<T>>>() == size_of::<Box<T>>()

2.2 特殊大小类型

DST(动态大小类型):编译期大小未知,只能活在指针之后;指向 DST 的指针是宽指针

1
2
3
4
5
6
7
8
// 宽指针 = 数据指针 + 补全信息(伪代码)
&dyn Trait == { data_ptr, vtable_ptr } // vtable 可查运行时大小
&[T] == { data_ptr, len } // 大小 = sizeof(T) × len
&str == { data_ptr, len } // str 是 [u8] 的 UTF-8 约定版

// 结构体可把单个 DST 放在最后一个字段(自身随之成为 DST)
struct MySuperSlice { info: u32, data: [u8] } // ✅ 合法但 MySuperSlice 成为 DST
struct Bad { data: [u8], info: u32 } // ❌ 编译错误:DST 只能在末尾

ZST(零大小类型):所有操作都是 no-op,泛型语境威力巨大:

1
2
3
4
5
6
struct Nothing;
let a = Nothing; let b = Nothing; // 复制是 no-op
size_of::<Nothing>() == 0

// 经典应用:Set = Map + ZST 值
type Set<K> = std::collections::HashMap<K, ()>; // 值零开销,等价定制 HashSet

空类型:永远无法实例化,用于类型层面表达"不可能":

1
2
3
4
5
6
enum Void {}                          // 无变体
let Ok(x) = res::<T, Void>; // ✅ 不可驳匹配:Err 变体不存在,编译通过

let p: *const Void = dangling_ptr();
unsafe { *p }; // ❌ UB:指向空类型的指针解引用是 UB
// ❌ 不要用 *const Void 模拟 C 的 void* ✅ 用 *const ()

2.3 其他 repr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#[repr(C)]                           // 照抄 C 布局:FFI 边界必加
struct PointC { x: f32, y: f32 }

#[repr(transparent)] // 与唯一非零字段布局/ABI 完全一致
struct Meters(f32) // 可与 f32 安全互转、按 f32 传 FFI

#[repr(u8)] // 固定枚举判别式大小
enum Color { Red = 0, Green, Blue }

#[repr(C, packed)] // 去填充(危险!对字段取引用 = 未对齐 = UB)
struct Wire { a: u8, b: u32 } // b 不再 4 字节对齐,占 5 字节

#[repr(align(64))] // 强制至少 64 字节对齐(如缓存行隔离)
struct CacheLine([u8; 64])
陷阱 说明
repr(u8) 抑制 niche #[repr(u8)] enum MyOption<&u16> 占 16 字节,不带则 8 字节(空指针优化被关掉)
非法枚举值 = UB unsafe { transmute::<i32, Color>(99) } ❌ 即使带 repr(C) 也不允许无对应变体的值
packed 取引用 let r = &w.b; ❌ 未对齐引用,未来是硬错误
Option 与 FFI Option<&T> / Option<extern fn>T 布局相同,可对应 C 的可空指针

第 3 章 Ownership(所有权)

3.1 引用:全部模型只有两条规则

1
2
3
4
5
6
7
8
// 规则一:引用不能活得比被引用者久
fn dangle() -> &String { &String::new() } // ❌ 返回局部变量引用(悬垂)

// 规则二:可变引用不能被别名化
let mut x = 0;
let r1 = &mut x;
let r2 = &mut x; // ❌ E0499:r1 存活期间再建 &mut
*r1 += 1;

3.2 别名 (Aliasing):优化的基石

指向重叠内存的指针互为别名。&mut 的无别名保证让编译器敢做激进优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
fn compute(input: &u32, output: &mut u32) {
if *input > 10 { *output = 1; }
if *input > 5 { *output *= 2; }
}
// 编译器可以优化成(因为 input/output 保证不别名):
fn compute_opt(input: &u32, output: &mut u32) {
let v = *input; // ① 只读一次,缓存进寄存器
if v > 10 { *output = 2; } // ② 两次写合并成一次
else if v > 5 { *output = 2; } // (无中间读回 *output)
}
// 若允许 compute(&x, &mut x):写 *output 会改 *input,上述优化 ❌ 不再等价

// UnsafeCell 是唯一例外:允许 & 引用的数据被修改(内部可变性的根基)

3.3 生命周期与 NLL

生命周期 = 引用必须有效的区域;借用活到最后一次使用(NLL),不是作用域结束:

1
2
3
4
let mut data = vec![1, 2, 3];
let x = &data[0];
println!("{}", x); // ← x 的最后一次使用,借用到此为止
data.push(4); // ✅ OK:x 已死,尽管变量名还在作用域内
flowchart LR
    A["x 借用 data"] -->|"借用开始"| B["最后一次使用 x"]
    B -->|"借用结束"| C["x 不再活跃"]
    C -->|"data 恢复独占"| D["data.push(4) 合法"]
    D --> E["作用域结束<br/>与借用无关"]

陷阱:Drop 被视为最后一次使用

1
2
3
4
5
6
struct X<'a>(&'a i32);           // 带 Drop glue
let x = X(&data[0]);
println!("{}", x.0);
data.push(4); // ❌ 编译失败:x 的析构在作用域末尾会读 self.0
drop(x); // ✅ 先手动终结借用
data.push(4);

3.4 生命周期的局限(系统比语义粗)

1
2
3
4
5
6
7
8
9
10
struct Foo;
impl Foo {
fn mutate_and_share(&mut self) -> &Self { &*self } // 脱糖:&'a mut self -> &'a Self
fn share(&self) {}
}
let mut foo = Foo;
let loan = foo.mutate_and_share(); // 可变借用被迫活到 loan 最后使用
foo.share(); // ❌ E0502:&mut 仍存活,不能再 &self
println!("{:?}", loan);
// 真实引用语义下程序正确——生命周期系统粒度不足,保守拒绝

3.5 生命周期省略规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 规则一:每个省略的输入生命周期各自独立
fn f(x: &str, y: &str) // => fn f<'a, 'b>(x: &'a str, y: &'b str)

// 规则二:只有一个输入 → 赋给所有省略的输出
fn substr(s: &str, until: usize) -> &str
// => fn substr<'a>(s: &'a str, until: usize) -> &'a str ✅

// 规则三:多个输入 + &self → self 的生命周期赋给输出
fn f(&mut self, args: &[T]) -> &mut Command
// => fn f<'a, 'b>(&'a mut self, ...) -> &'a mut Command ✅

// 规则四:其他情况省略输出 = 编译错误
fn get_str() -> &str // ❌ E0106:无法确定输出生命周期
fn frob(s: &str, t: &str) -> &str // ❌ 两个输入,输出不知道跟谁

3.6 无界生命周期

规则:凡不派生于输入的输出生命周期都是无界的——比 'static 还强:

1
2
3
4
5
6
7
fn get_str<'a>(s: *const String) -> &'a str {
unsafe { &*s } // 'a 凭空捏造 → 调用方可以让它指向任何时刻
} // 调用后 drop 原对象 → 悬垂引用 💀

// ✅ 对策:尽快在函数边界约束(利用省略规则)
fn get_str_safe(s: &String) -> &str { unsafe { &*(s as *const String) } }
// 输出生命周期被输入约束 → 绑错最多编译错误,不会内存安全事故

3.7 高阶 trait 约束 (HRTB)

1
2
3
4
// F 需要对"任意"生命周期参数成立,普通脱糖无法命名这个生命周期
fn foo<F>(f: F) where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8
// ^^^^^^ for<'a> = "对所有 'a",等价无限条约束:
// F: Fn<&'x T> -> &'x u8 对每一个可能的 'x 都要成立

3.8 子类型与可变性 (Variance)

'long <: 'short(活得久的是子类型,反直觉但正确)

1
2
3
let s: &'static str = "hi";
fn takes_short<'a>(s: &'a str) {}
takes_short(s); // ✅ &'static <: &'a:活得久当然能当活得短用

必背 variance 表

类型 'a T U
&'a T 协变 协变
&'a mut T 协变 不变
Box<T> / Vec<T> 协变
UnsafeCell<T> / Cell<T> 不变
fn(T) -> U 逆变 协变
*const T 协变
*mut T 不变

为什么 &mut T 必须不变——否则经典 UAF:

1
2
3
4
5
6
7
8
fn assign<T>(input: &mut T, val: T) { *input = val; }
let mut hello: &'static str = "hello";
{
let world = String::from("world");
// 若 &mut T 协变:&mut &'static str 可以降级当 &mut &'short str 用
assign(&mut hello, &world); // ❌ 被不变性阻止:world 死后 hello 悬垂
}
println!("{}", hello); // 💀 use-after-free(若没被阻止)
1
2
3
4
5
// 函数参数逆变的直觉
fn store(f: fn(&'static str)) {} // 需要"能处理 &'static"的函数
fn any_life<'a>(s: &'a str) {} // 能处理任意生命周期
store(any_life); // ✅ 逆变:更通用可替代更具体
// 反方向则 ❌:把短命引用塞进只懂 'static 的函数 → 悬垂

冲突法则:参数用于多个字段时,全协变才协变、全逆变才逆变、否则不变(不变赢得一切冲突)。

3.9 Drop 检查 (dropck)

Big Rule:泛型类型的泛型参数必须严格比它活得久 (strictly outlive)

1
2
3
4
5
6
7
8
struct Inspector<'a>(&'a u8);
impl Drop for Inspector<'_> {
fn drop(&mut self) { println!("{}", self.0); } // 析构中读取借用数据
}
struct World<'a> { inspector: Option<Inspector<'a>>, days: Box<u8> }
// World 析构顺序:inspector 先于 days(定义序)?
// 若 days 实际先被释放 → Inspector::drop 读到悬垂 &u8 → 💀
// 编译器因此要求:'a 严格长于 World —— 无 Drop 时该程序可以编译,有 Drop 则被拒

逃生舱 #[may_dangle](承诺析构不碰过期数据,编译器不检查):

1
2
3
4
5
unsafe impl<#[may_dangle] 'a> Drop for Inspector<'a> {
fn drop(&mut self) { } // ✅ 什么都不读 → 承诺成立
// fn drop(&mut self) { println!("{}", self.0); } // ❌ 承诺被自己打破 → UB
}
// ⚠️ 间接访问也算:析构里调用 <T as Display>::fmt / 回调函数都会触达 T

3.10 PhantomData

问题:生命周期与结构体逻辑相关却不出现在字段里(无界参数在 struct 定义中被禁止):

1
2
3
4
5
struct Iter<'a, T: 'a> {
ptr: *const T, // 裸指针不带生命周期
end: *const T,
_marker: PhantomData<&'a T>, // ✅ 零大小,向类型系统"模拟"一个 &'a T 字段
} // 一次搞定 variance + dropck + auto trait

使用模式速查

Phantom 类型 效果(对 T 的 variance / Send+Sync / drop 悬垂许可)
PhantomData<T> 协变 / 继承 / 不允许(声明"拥有 T")
PhantomData<&'a T> 协变 / 需 T: Sync / 允许
PhantomData<&'a mut T> 不变 / 继承 / 允许
PhantomData<*const T> 协变 / !Send+!Sync / 允许
PhantomData<*mut T> 不变 / !Send+!Sync / 允许
PhantomData<fn(T)> 逆变 / Send+Sync / 允许
PhantomData<fn() -> T> 协变 / Send+Sync / 允许

退出 Unpin 用专用类型 PhantomPinned;已有 Drop impl 的类型再加 PhantomData<T> 对 dropck 是多余的,但 variance/auto trait 效果仍在。

3.11 拆分借用

借用检查器懂结构体字段、不懂数组元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let mut x = (1, 2);
let a = &mut x.0; let b = &mut x.1; // ✅ 不相交字段,同时可变借用
let mut v = vec![1, 2];
let a = &mut v[0]; let b = &mut v[1]; // ❌ E0499:borrowck 不懂数组下标

// 教 borrowck 的 unsafe 范例:split_at_mut 构造两个全新切片
pub fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T]) {
let len = self.len(); let ptr = self.as_mut_ptr();
unsafe {
assert!(mid <= len);
(from_raw_parts_mut(ptr, mid), // 左半 [0, mid)
from_raw_parts_mut(ptr.add(mid), len - mid)) // 右半 [mid, len)
}
}

惊喜结论IterMut 可用纯 Safe 代码实现——关键是"每个元素至多被产出一次":

1
2
3
4
5
6
7
// 链表 IterMut 的安全实现核心:Option::take 永久消耗引用
fn next(&mut self) -> Option<&'a mut T> {
self.cur.take().map(|node| { // take 把 Option 清空 → 引用被移出
self.cur = node.next.as_mut(); // 指向下一个(此时旧 &mut 已消耗)
&mut node.elem // 每次产出全新的 &mut,从不同时存在两个
})
}

第 4 章 Type Conversions(类型转换)

转换光谱(安全 → 危险):解构重建 → 强制转换 → 点操作符 → as → transmute。

4.1 强制转换

1
2
3
4
5
6
7
8
9
10
let x: &i32 = &mut 0;        // ✅ 隐式强转 &mut i32 → &i32("弱化")
let v: Vec<i32> = vec![];
let s: &[i32] = &v; // ✅ &Vec<i32> → &[i32](Deref 强转)

// ⚠️ 关键规则:trait 匹配时不做强转
trait Trait {}
impl Trait for &i32 {}
fn foo<T: Trait>(t: T) {}
foo(&mut 0); // ❌ 编译错误:&mut i32 未实现 Trait
// 编译器不会替你强转成 &i32 再匹配

4.2 点操作符

方法查找算法(伪代码):

1
2
3
4
5
6
7
value.foo() 解析顺序:
1. T::foo(value) 按值调用
2. <&T>::foo(&value) autoref 共享
<&mut T>::foo(&mut value) autoref 可变
3. 对 Deref 目标 U 重复 1-2 解引用(Box→T, Rc→T...)
4. unsize: [T; 3] → [T] 忘掉编译期长度
5. 全部失败 → 编译错误

autoref 陷阱derive(Clone) 附带 T: Clone 约束,泛型下 clone 克隆出引用:

1
2
3
4
5
6
7
8
9
10
11
#[derive(Clone)]
struct Container<T>(Arc<T>); // derive 生成: impl<T: Clone> Clone for Container<T>

fn f<T>(bar: &Container<T>) {
let c = bar.clone(); // ❌ T: Clone 不成立 → 退化为 autoref
// c 的类型是 &Container<T>(克隆了引用!)
}
// ✅ 修复:手写不带约束的实现
impl<T> Clone for Container<T> {
fn clone(&self) -> Self { Self(Arc::clone(&self.0)) }
}

4.3 as 转换

性质:运行时从不失败(截断/饱和静默发生);非法组合编译期拒绝;cast 本身不是 unsafe。

1
2
3
4
5
6
7
8
9
10
let a = 300i32 as u8;              // ✅ 44(截断,静默)
let b = 3.9_f64 as i32; // ✅ 3(截断)
let c = 7u8 as bool; // ❌ 编译错误:数值→bool 不合法
let d = 65u8 as char; // ✅ 'A'(仅 u8 允许)
let e = 65i32 as char; // ❌ 编译错误:非 u8 整数→char 不合法

// ⚠️ 裸切片 cast 不调整长度
let s: *const [u16] = ...;
let t = s as *const [u8]; // 长度(元素数)不变 → 只覆盖一半内存!
// ⚠️ cast 不可传递:e as U1 as U2 合法 ⇏ e as U2 合法

4.4 Transmute

mem::transmute<T, U> 按位重解释,唯一检查是尺寸相同——"最可怕的 unsafe 操作":

1
2
3
4
5
6
7
8
9
10
11
// ❌ 危险清单
unsafe { transmute::<u8, bool>(3) } // ❌ UB:产生非法 bool(不用也是 UB)
unsafe { transmute::<&i32, &mut i32>(r) } // ❌ 永远 UB:& → &mut
unsafe { transmute::<Vec<i32>, Vec<u32>>(v) } // ❌ UB:repr(Rust) 布局无保证
// 两个字段顺序可能不同
let r: &str = unsafe { transmute(ptr) }; // ❌ 无显式生命周期 → 无界生命周期

// ✅ 合法场景(布局确有保证)
unsafe { transmute::<[MaybeUninit<T>; N], [T; N]>(arr) } // 数组是特例:逐元素布局一致
#[repr(transparent)] struct Wrapper(i32);
unsafe { transmute::<i32, Wrapper>(x) } // ✅ transparent 保证与 i32 同布局

第 5 章 Uninitialized Memory(未初始化内存)

把未初始化内存当作任何类型的值解读 = UB

5.1 检查式:Safe Rust 的初始化分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let x: i32;
if cond { x = 1; } else { x = 2; } // ✅ 每个分支恰好初始化一次(延迟初始化不需要 mut)
println!("{}", x);

let y: i32;
if cond { y = 1; } // ❌ 编译错误:else 分支未初始化
println!("{}", y);

let z;
loop { if true { z = 0; break; } } // ✅ 编译器懂控制流:不 break 到不了后面
println!("{}", z);

let s = String::from("a");
let t = s; // s 被移走 → 逻辑上未初始化
// println!("{}", s); // ❌ E0382
// s = String::from("b"); // 重新赋值需要声明成 mut

5.2 Drop 标志

编译器用运行时 drop flag 跟踪"该不该调析构器":

1
2
3
4
5
6
7
let x;
if cond { x = Box::new(0); } // 条件初始化
// ← 作用域结束:cond 为 false 时 x 未初始化,不能调用 drop
// → 编译器生成: if (drop_flag) { drop(x) } 运行时判断

let y = Box::new(0); // let 声明 → 无条件 drop(静态可知,零开销)
*y2 = Box::new(1); // 解引用赋值 → 无条件 drop 旧值
flowchart TD
    A["变量离开作用域"] --> B{"初始化状态静态可知?"}
    B -->|"直线代码 或 分支一致"| C["静态 drop<br/>零运行时开销"]
    B -->|"条件初始化 或 提前 return"| D["运行时 drop flag<br/>判断后才调用析构器"]

5.3 非检查式与 MaybeUninit

三步初始化模式(Safe Rust 无法部分初始化数组):

1
2
3
4
5
6
7
8
9
// ① 创建 MaybeUninit 数组(此处的 assume_init 安全:MaybeUninit 本不需要初始化)
let mut arr: [MaybeUninit<Box<u32>>; N] =
unsafe { MaybeUninit::uninit().assume_init() };

// ② 逐元素填充(MaybeUninit 的 drop 是 no-op,= 赋值安全)
for i in 0..N { arr[i] = MaybeUninit::new(Box::new(i as u32)); }

// ③ 全部就绪后 transmute 成真实数组(MaybeUninit<T> 与 T 布局一致,数组是特例)
let arr: [Box<u32>; N] = unsafe { mem::transmute(arr) };
stateDiagram-v2
    state "未初始化" as s1
    state "部分初始化" as s2
    state "完全初始化" as s3
    [*] --> s1: uninit 创建
    s1 --> s2: 逐元素填充
    s2 --> s2: 继续填充
    s2 --> s3: 最后一个元素就绪
    s3 --> [*]: assume init 或 transmute
    note right of s2: panic 时最多泄漏已初始化部分

关键工具与规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ptr 系:绕过赋值的"读旧值 + drop"语义
ptr::write(ptr, val); // 初始化:盲目按比特写入,不求值旧值
let v = ptr::read(ptr); // 移出:按比特取出,原位置逻辑未初始化
ptr::copy(src, dst, n); // memmove(⚠️ 参数顺序与 C 相反)
ptr::copy_nonoverlapping(src, dst, n); // memcpy

// ❌ 禁止构造指向未初始化数据的引用(即使是临时引用)
let mut u = MaybeUninit::<Demo>::uninit();
let r = unsafe { &u.as_mut().field }; // ❌ UB:&bool 指向未初始化内存
let p = unsafe { &raw mut (*u.as_mut_ptr()).field }; // ✅ 裸指针不建立引用
unsafe { p.write(true); }
let demo = unsafe { u.assume_init() }; // ✅ 此时才承诺初始化完成

// ⚠️ Container<MaybeUninit<T>> 布局 ≠ Container<T>
// Option<bool> 用 niche 存 None;Option<MaybeUninit<bool>> 不能 → 尺寸可能不同
// ❌ mem::uninitialized() 已废弃,一律用 MaybeUninit

第 6 章 OBRM(基于所有权的资源管理 / RAII)

获取资源 = 创建对象;释放资源 = 销毁对象。Rust 关注控制权

6.1 构造器

创建实例的方式有且只有一种——命名类型并一次性初始化全部字段:

1
2
3
4
5
6
7
8
9
10
11
12
let foo = Foo { a: 0, b: 1 };   // 唯一真构造器,其他构造方式都归结到它

// 惯用的 new 只是命名约定,无语言特殊语义
impl Foo {
pub fn new() -> Self { Foo { a: 0, b: 1 } }
pub fn with_capacity(n: usize) -> Self { ... }
}

// Rust 没有任何内建构造器:
// ❌ 无拷贝构造器(Clone 是普通 trait,从不隐式调用)
// ❌ 无移动构造器(所有类型必须能被盲目 memcpy —— 类型不能"在意"自己的位置)
// → "栈上可移动的侵入式链表"在安全 Rust 中不可实现

6.2 析构器:递归 drop 与 double-free

drop 运行后 Rust 会自动递归 drop 所有字段(不可关闭):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ❌ double-free 反例:SuperBox 越俎代庖释放了内层 Box
impl<T> Drop for SuperBox<T> {
fn drop(&mut self) {
unsafe { dealloc(self.my_box.ptr); } // 我释放了内存
} // 随后递归 drop 又执行 Box::drop
} // → 二次释放同一内存 💀

// ✅ 经典解法:Option::take + mem::forget
struct SuperBox<T> { my_box: Option<Box<T>> }
impl<T> Drop for SuperBox<T> {
fn drop(&mut self) {
if let Some(b) = self.my_box.take() { // ① 先把字段取空 → 递归 drop 无事可做
drop(b); // ② 正常释放(Box 自己处理)
}
}
}
flowchart TD
    A["SuperBox 离开作用域"] --> B["执行 SuperBox 的 drop"]
    B --> C{"my_box 字段状态?"}
    C -->|"未 take,仍是 Some"| D["递归 drop 执行 Box 的 drop"]
    B2["自己又 dealloc 了同一指针"] --> E["double-free 崩溃"]
    C -->|"已 take 成 None"| F["递归 drop 无事可做"]

6.3 泄漏:mem::forget 是安全函数

铁律:unsafe 代码绝不能把"析构器一定运行"作为安全前提

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mem::forget(my_box);   // ✅ 安全函数:消费值且不运行析构器
// 曾标 unsafe,但安全代码有太多泄漏途径(Rc 循环引用)

// 真正危险的是"代理类型":管理访问却不拥有资源
// 案例一:Drain —— 迭代时 forget 掉它会暴露未初始化内存
impl<'a, T> Drain<'a, T> {
pub fn drain(vec: &'a mut Vec<T>) -> Self {
// ✅ 对策:入口立即把 Vec 清空(leak amplification)
let iter = ...;
vec.len = 0; // 被 forget 时最坏泄漏整个 Vec(安全)
Drain { vec, iter } // 绝不会读到已移出的悬垂元素(UB)
}
}
// 案例二:Rc —— 多次 forget 使计数回绕 → 递减到 0 时仍有句柄存活
// ✅ 对策:计数接近 isize::MAX 时 abort
// 案例三:JoinGuard —— 析构器是安全根基 → API 推倒重设计(thread::scope)

第 7 章 Unwinding(栈展开)

7.1 展开机制

分层错误处理:Option(缺失)→ Result(可恢复)→ panic(不可恢复)→ abort(灾难)。

1
2
3
4
panic!("boom");
// 展开栈:每个函数像立即返回一样依次执行析构器 → 资源不泄漏
// 但:从外部语言展开进 Rust / 从 Rust 展开进外部语言 = ❌ UB
// 必须在 FFI 边界捕获(catch_unwind 或 "C-unwind" ABI)

7.2 两级异常安全

  • 最小异常安全(unsafe 必须):panic 不违反内存安全(状态"安全",不必一致)
  • 最大异常安全(safe 最好):panic 后程序仍做正确的事
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ❌ 违反最小异常安全的 push_all
fn push_all(&mut self, other: &[T]) {
unsafe { self.set_len(self.len + other.len()); } // ① 先改 len
for (i, x) in other.iter().enumerate() {
self.ptr.add(i).write(x.clone()); // ② clone 是用户代码,可能 panic!
} // panic 提前退出 → len 已虚高 → 未初始化内存暴露 → 💀
}

// ✅ 修复:把 set_len 挪到全部初始化完成之后
fn push_all(&mut self, other: &[T]) {
for (i, x) in other.iter().enumerate() {
if self.len == self.cap { self.grow(); }
unsafe { ptr::write(self.ptr.add(self.len), x.clone()); }
self.len += 1; // 每轮迭代更新:panic 时已写入的都能被正确 drop
}
}

// ✅ 另一种修复:Hole 结构体(模拟 try/finally)
struct Hole<'a, T> { elem: &'a mut T } // 析构器无条件把元素填回洞中
impl<T> Drop for Hole<'_, T> { fn drop(&mut self) { /* 填洞 */ } }
// panic 展开时 Drop 照常运行 → 洞永远被填上 → 无 double-drop

7.3 Poison中毒

1
2
3
4
5
let guard = mutex.lock().unwrap();   // 持锁期间 panic → MutexGuard 在展开中 drop
// → Mutex 标记中毒
let guard2 = mutex.lock(); // Err(PoisonError) —— 之后所有 lock 都失败
// 中毒 ≠ 内存安全问题;目的是阻止使用不一致状态的数据(如失去堆性质的 BinaryHeap)
// 确信能处理:PoisonError::into_inner() 强行取出数据(依然"安全",可能"无意义")

第 8 章 Concurrency(并发)

8.1 数据竞争 vs 竞态条件

数据竞争 = UB,三条同时满足:并发访问同一位置 + 至少一个写 + 至少一个非同步。

1
2
3
4
5
6
7
8
// ✅ 安全:load 一次存局部变量,检查和使用同一份数据
let i = idx.load(Ordering::SeqCst);
println!("{}", data[i]); // 最坏 panic(越界),不违反内存安全

// ❌ TOCTOU:两次 load 之间值可能已变
if idx.load(Ordering::SeqCst) < data.len() {
unsafe { data.get_unchecked(idx.load(Ordering::SeqCst)) } // 💀 第二个值可能越界
}

Rust 不阻止一般竞态条件(死锁等)——那是"安全"的,只是程序可能不对。

8.2 Send 与 Sync

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义:Send = 可移动到别的线程;Sync = 可在线程间共享(&T: Send ⟺ T: Sync)
// unsafe marker trait:错误实现 = UB

unsafe impl<T: Send> Send for Carton<T> {} // 论证:独占指针,随值移动
unsafe impl<T: Sync> Sync for Carton<T> {} // 论证:无内部可变性,写全走 &mut

// 自动派生规则(伪代码):
// T: Send ⟺ 所有字段: Send → 几乎所有类型自动满足
// 例外:*const/*mut T(!Send+!Sync)、UnsafeCell/Cell/RefCell(!Sync)、Rc(!Send+!Sync)

// 反例:MutexGuard 为什么 !Send
// pthread 要求解锁线程 == 加锁线程 → 跨线程 drop guard 会违规
// 但它是 Sync:跨线程传 &MutexGuard 什么也 drop 不了

8.3 原子操作与内存顺序

四种 Ordering(Rust 继承 C++20 内存模型):

1
2
3
4
5
6
7
8
9
10
// SeqCst:拿不准就选它——"稍慢"好过"错误"
x.store(1, Ordering::SeqCst);

// Acquire/Release:成对使用,天生适合锁
while lock.compare_and_swap(false, true, Ordering::Acquire) {} // 加锁
/* ... 受保护的数据访问 ... */
lock.store(false, Ordering::Release); // 解锁

// Relaxed:无顺序保证但仍是原子(不构成数据竞争)—— 适合纯计数
counter.fetch_add(1, Ordering::Relaxed);
graph LR
    subgraph 线程A
        A1["写数据 data=42"] --> A2["lock.store<br/>Release"]
    end
    subgraph 线程B
        B2["lock.load<br/>Acquire"] --> B1["读数据<br/>保证看到 42"]
    end
    A2 -->|"同步边 happens-before"| B2

经验法则:SeqCst 保底;锁用 Acquire/Release;纯计数用 Relaxed。并发算法要在弱序硬件(ARM)上测试——x86 强序上可能"碰巧能跑"。


第 9 章 Implementing Vec(实现 Vec)

用稳定 API(NonNullstd::allocptr 系)从零手写 std::Vec

9.1 布局

1
2
3
4
5
6
7
pub struct Vec<T> {
ptr: NonNull<T>, // ✅ 而非 *mut T:裸指针 variance 过严 + 可为 null
cap: usize, // NonNull 声明:协变 + 永不为 null(Option<Vec> 免费空指针优化)
len: usize,
}
unsafe impl<T: Send> Send for Vec<T> {} // 手动补齐裸指针丢失的 auto trait
unsafe impl<T: Sync> Sync for Vec<T> {}

9.2 分配:三个坑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 坑一:0 字节分配是 UB,但 NonNull 又不能是 null
impl<T> RawVec<T> {
fn new() -> Self {
let ptr = NonNull::dangling(); // ✅ 对齐的非空"垃圾"指针占位
Self { ptr, cap: 0 } // cap == 0 作"未分配"哨兵
}
}

// 坑二:GEP inbounds —— offset 承诺偏移在同一分配内,且分配 ≤ isize::MAX 字节
let layout = Layout::array::<T>(cap).unwrap(); // 内部会断言不超 isize::MAX
assert!(layout.size() <= isize::MAX as usize); // 32 位 + PAE 需无条件检查

// 坑三:OOM 用 abort 而非 panic(展开本身可能再分配)
let ptr = unsafe { alloc(layout) };
if ptr.is_null() { handle_alloc_error(layout); } // ✅ 直接 abort

9.3 Push 与 Pop:ptr::write / read

核心区别:赋值会"求值旧值 + drop",ptr::write 只盲目覆写

1
2
3
4
5
6
7
8
9
10
pub fn push(&mut self, elem: T) {
if self.len == self.cap { self.grow(); }
unsafe { ptr::write(self.ptr.as_ptr().add(self.len), elem); } // 目标内存未初始化
self.len += 1; // 用 = 赋值会 read+drop 旧残骸 → 💀
}
pub fn pop(&mut self) -> Option<T> {
if self.len == 0 { return None; }
self.len -= 1; // 先减
unsafe { Some(ptr::read(self.ptr.as_ptr().add(self.len))) } // 按比特移出
} // 原位置从此逻辑未初始化(所有权已转移给调用者)

9.4 释放

1
2
3
4
5
6
7
8
impl<T> Drop for Vec<T> {
fn drop(&mut self) {
while let Some(_) = self.pop() {} // 逐个 drop 元素(LLVM 会优化掉无 Drop 的循环)
if self.cap != 0 { // ✅ cap==0 绝不能 dealloc(没有真实分配)
unsafe { dealloc(self.ptr.as_ptr(), Layout::array::<T>(self.cap).unwrap()); }
}
}
}

9.5 Deref:免费继承切片全家桶

1
2
3
4
5
6
7
8
impl<T> Deref for Vec<T> {
type Target = [T];
fn deref(&self) -> &[T] {
unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.len) } // 凭 (ptr,len) 现造切片
}
}
// 一次性获得:len / first / 索引 / 切片 / sort / iter / iter_mut / contains...
// 空切片与 ZST 自动正确(len=0 → 空切片;ZST → 布局规则保证)

9.6 Insert 与 Remove

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ptr::copy = memmove,正确处理重叠区间(⚠️ 参数顺序与 C 相反:dst 在前)
pub fn insert(&mut self, index: usize, elem: T) {
assert!(index <= self.len); // 末尾插入合法(== push)
if self.len == self.cap { self.grow(); }
unsafe {
ptr::copy(self.ptr.as_ptr().add(index), // [index..len]
self.ptr.as_ptr().add(index + 1), // 右移一格 → [index+1..len+1]
self.len - index);
ptr::write(self.ptr.as_ptr().add(index), elem);
}
self.len += 1;
}
pub fn remove(&mut self, index: usize) -> T {
assert!(index < self.len);
self.len -= 1; // 先减
unsafe {
let elem = ptr::read(self.ptr.as_ptr().add(index)); // ① 移出目标
ptr::copy(self.ptr.as_ptr().add(index + 1), // ② 左移补洞
self.ptr.as_ptr().add(index), self.len - index);
elem
}
}

9.7 IntoIter:接管分配 + 双指针

1
2
3
4
5
6
7
8
9
10
pub struct IntoIter<T> { buf: RawVec<T>, iter: RawValIter<T> }

impl<T> Vec<T> {
pub fn into_iter(self) -> IntoIter<T> {
let vec = ManuallyDrop::new(self); // ✅ 防止 Vec 自身析构释放缓冲区
let iter = RawValIter::new(unsafe { &*vec }); // 否则双重释放 💀
let buf = unsafe { ptr::read(&vec.buf) }; // 把 RawVec 按比特移出
IntoIter { buf, iter } // 分配所有权转移给 IntoIter
}
}

双指针迭代器与 next_back 的坑

1
2
3
4
5
6
7
8
9
10
11
12
13
内存布局:[ X, X, X, O, X, X, X ]

只剩一个元素时:start 在 O 左,end 在 O 右 → start != end → 可区分"还有 1 个"
❌ 若 end 直接指向待读元素:start == end 与"耗尽"无法区分
✅ next_back 必须"先退一格,再读":
fn next_back(&mut self) -> Option<T> {
if self.end == self.start { None }
else {
self.end = self.end.sub(1); // 先退
unsafe { Some(ptr::read(self.end)) } // 后读
}
}
// ⚠️ cap == 0 时 end 不许 offset(dangling 不属于任何分配,GEP 会撒谎)

9.8 RawVec:抽象重复

1
2
3
4
5
6
7
8
struct RawVec<T> { ptr: NonNull<T>, cap: usize }  // 集中 new / grow / Drop(dealloc)
pub struct Vec<T> { buf: RawVec<T>, len: usize } // Vec::drop 只管 pop 元素
// 释放内存全权交给 RawVec

// into_iter 防双重释放的经典三步:
let vec = ManuallyDrop::new(self); // ① 冻结 Vec 析构
let iter = RawValIter::new(&vec); // ② 从冻结的 Vec 建迭代器
let buf = ptr::read(&vec.buf); // ③ 按比特移出 RawVec(所有权转移)

9.9 Drain 与泄漏放大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pub struct Drain<'a, T> {
vec: *mut Vec<T>, // 不带生命周期(见下)
_marker: PhantomData<&'a mut Vec<T>>, // ✅ 用 PhantomData 表达"我持有 &mut 借用"
}
// RawValIter::new 是 unsafe fn:不带生命周期参数,
// 才能与它指向的分配存进同一个结构体(这是它的契约)

impl<'a, T> Vec<T> {
pub fn drain(&mut self) -> Drain<'a, T> {
let iter = RawValIter::new(self);
unsafe { self.set_len(0); } // ✅ 泄漏放大:入口立即清空
Drain { vec: self as *mut _, iter, _marker: PhantomData }
}
}
// 用户 mem::forget(drain) → Drain::drop 不执行 → 最坏泄漏整个 Vec(安全)
// ↳ 若不提前清空 → 已移出区域暴露 → vec[0] 读到悬垂指针 💀

9.10 零大小类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 坑一:分配 —— ZST 的 0 字节分配是 UB,但 usize::MAX 个 ZST 又不耗内存
impl<T> RawVec<T> {
fn new() -> Self {
if size_of::<T>() == 0 {
Self { ptr: NonNull::dangling(), cap: usize::MAX } // ✅ 一指针两用:
} // "未分配"兼"零尺寸分配";cap 顶格 → 编译器剥离永不扩容分支
else { /* 真分配 */ }
}
fn grow(&mut self) {
if size_of::<T>() == 0 {
panic!("cap overflow"); // ✅ 必须显式防溢出!"反正先 OOM"对 ZST 失效
} // ...
}
}

// 坑二:迭代 —— ZST 指针 offset 是 no-op,双指针永相等 → 迭代器空转
struct RawValIter<T> { start: *const T, end: *const T }
impl<T> Iterator for RawValIter<T> {
fn next(&mut self) -> Option<T> {
if size_of::<T>() == 0 {
if self.start == self.end { return None; }
self.start = (self.start as usize + 1) as *const T; // ✅ 转成 usize 当计数器
// ⚠️ 书中自曝 bug:计数器递增后指针失去对齐!
// ✅ 修正:始终从 dangling 指针读
return unsafe { Some(ptr::read(NonNull::<T>::dangling().as_ptr())) };
}
// 非 ZST 正常路径...
}
}

第 10 章 Implementing Arc(实现 Arc)

简化版 Arc(无 weak;原书 Mutex 部分尚未发布)。

10.1 布局:计数与数据同块分配

1
2
3
4
5
6
7
8
9
10
11
12
pub struct Arc<T> {
ptr: NonNull<ArcInner<T>>, // 协变 + 非空
phantom: PhantomData<ArcInner<T>>, // ✅ 告诉 dropck:"我拥有 ArcInner<T>"
}
struct ArcInner<T> {
rc: AtomicUsize, // 引用计数:共享可变状态 → 必须原子
data: T,
}
// 论证 unsafe impl:唯一引用只可能出现在 Drop 中(才有 &mut),计数全程原子
unsafe impl<T: Sync + Send> Send for Arc<T> {}
unsafe impl<T: Sync + Send> Sync for Arc<T> {}
// 若无 bound:Arc<Rc<u32>> 会成为 Send → Rc 被跨线程克隆 → 数据竞争 💀

10.2 Clone:Relaxed + 防回绕

1
2
3
4
5
6
7
8
9
10
impl<T> Clone for Arc<T> {
fn clone(&self) -> Self {
let inner = unsafe { self.ptr.as_ref() };
let old = inner.rc.fetch_add(1, Ordering::Relaxed); // ✅ Relaxed 足够:
if old >= isize::MAX as usize { // clone 不碰数据,无需同步
std::process::abort(); // ✅ 防计数回绕:mem::forget 可使计数虚低,
} // 无限 clone → 溢出回绕 → 提前归零 → UAF
Self { ptr: self.ptr, phantom: PhantomData }
}
}

10.3 Drop:Release 递减 + Acquire 栅栏

1
2
3
4
5
6
7
8
impl<T> Drop for Arc<T> {
fn drop(&mut self) {
let inner = unsafe { self.ptr.as_ref() };
if inner.rc.fetch_sub(1, Ordering::Release) != 1 { return; } // 非最后引用
atomic::fence(Ordering::Acquire); // ✅ 关键栅栏
unsafe { Box::from_raw(self.ptr.as_ptr()); } // 最后一个 → 释放
}
}
graph LR
    subgraph 线程A
        A1["写 data 经 Mutex"] --> A2["fetch_sub<br/>Release"]
    end
    subgraph 线程B
        B2["fetch_sub 返回 1"] --> B3["fence Acquire"] --> B4["删除 data"]
    end
    A2 -->|"happens-before"| B3
    A1 -. 保证可见 .-> B4

为什么需要 Acquire:Arc<Mutex<T>> 场景下,Mutex 析构时不加锁——不能指望锁自己的同步逻辑。必须由 Release 递减 + Acquire 栅栏保证"他人对数据的写"对"删除数据的人"可见。


第 11 章 FFI(外部函数接口)

11.1 调用外部函数 + 安全封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#[link(name = "snappy")]
extern "C" { // ⚠️ 默认 unsafe;编译器不检查声明正确性(签名自己核对)
fn snappy_compress(input: *const u8, in_len: size_t,
output: *mut u8, out_len: *mut size_t) -> c_int;
}

// 安全封装范式:签名不写 unsafe = 承诺对所有输入安全
pub fn compress(src: &[u8]) -> Vec<u8> {
unsafe {
let max = snappy_max_compressed_length(src.len());
let mut dst = Vec::with_capacity(max as usize);
let mut len = 0;
snappy_compress(src.as_ptr(), src.len(), dst.as_mut_ptr(), &mut len);
dst.set_len(len as usize); // ✅ C 写完之后才设置长度(之前 dst 逻辑上未初始化)
dst
}
}

11.2 从 C 调用 Rust、回调与链接

1
2
3
4
5
6
7
8
9
10
11
#[unsafe(no_mangle)]                  // 关闭名称修饰 → C 可链接的确定符号
pub extern "C" fn hello_from_rust() { println!("Hello from Rust!"); }
// Cargo.toml: crate-type = ["cdylib"]
// C 侧: extern void hello_from_rust();

// 回调:Rust 函数标 extern(正确调用约定)后注册
extern "C" fn on_event(data: c_int) { /* ... */ }
unsafe { register_callback(Some(on_event)); }

// 异步回调(C 自建线程回调)尤其危险:
// ✅ 用 channel/mutex 转发数据;对象销毁前必须注销回调

11.3 与外部代码互操作

1
2
3
4
5
6
7
8
9
#[repr(C)] struct Point { x: f32, y: f32 }   // ✅ FFI 类型必加

// Rust 字符串无 \0 结尾 → 跨界必须转换
let c = CString::new("hello").unwrap(); // ✅
c_str.as_ptr() // 传给 C

// 空指针优化:Option 直接对应 C 的可空指针,无需 transmute
extern "C" { fn set_cb(cb: Option<extern "C" fn(c_int) -> c_int>); }
// Option<extern fn> 与 C 的 int (*)(int) 布局相同 ✅

11.4 FFI 与展开

1
2
3
4
5
6
7
8
9
10
11
12
// 展开跨越 extern "C" 边界 = ❌ UB(外来异常进 Rust 同理)
// 预期有 panic/C++ 异常跨界 → 用 "C-unwind"
extern "C-unwind" fn may_panic() { panic!("..."); } // 允许展开传出
// 不预期跨界 → 普通 "C":panic 到边界时进程安全 abort(而非 UB)

// 导出函数中提前拦截:
#[unsafe(no_mangle)]
pub extern "C" fn api() {
if let Err(_) = catch_unwind(|| { real_api() }) { // 拦截 unwinding panic
eprintln!("rust panicked"); // (abort 类 panic 拦不住)
}
}

11.5 不透明结构体

1
2
3
4
5
6
7
8
9
10
// ❌ *mut c_void:无类型安全
// ✅ 自造不透明类型:
#[repr(C)]
pub struct Foo {
_data: (),
_marker: PhantomData<(*mut u8, PhantomPinned)>, // 阻止自动派生 Send/Sync/Unpin
}
// 私有字段 + 无构造器 → 外部无法实例化 → 只能持有 *mut Foo
// Foo 与 Bar 是不同类型 → 天然类型安全
// ⚠️ 切勿用空 enum 做 FFI 类型:&Empty 极易触发 UB

第 12 章 Beneath std(深入 std 之下)

12.1 no_std 可执行文件模板

1
2
3
4
5
6
7
8
9
10
11
#![no_std]
#![no_main]

#[unsafe(no_mangle)]
extern "C" fn main(_argc: c_int, _argv: *const *const c_char) -> c_int { 0 }

#[lang = "eh_personality"] // 多数平台需要(不稳定,常需 nightly)
fn rust_eh_personality() {}

#[panic_handler] // no_std 必须自定义 panic 行为
fn panic(_info: &PanicInfo) -> ! { core::intrinsics::abort() }
1
2
3
4
⚠️ 要点:
- 依赖 libc 时 default-features = false(libc 默认特性包含 std!)
- windows-msvc 目标没有 libc(extern crate libc 反而报错)
- 自建 core 的目标可能要手动链接 compiler_builtins(解决 __aeabi_memcpy 未定义)

12.2 panic crate 模式

1
2
3
4
5
6
7
8
9
10
11
12
// 每种行为一个小 crate,只含一个 #[panic_handler](依赖图中只能出现一次)
// panic-halt/src/lib.rs
#[panic_handler]
fn panic(_: &PanicInfo) -> ! { loop {} } // 静默停机

// panic-semihosting/src/lib.rs
#[panic_handler]
fn panic(info: &PanicInfo) -> ! { /* 输出到宿主机调试器 */ }

// app/src/main.rs —— 按 profile 链接不同 panic crate
#[cfg(debug_assertions)] extern crate panic_semihosting; // dev:打印
#[cfg(not(debug_assertions))] extern crate panic_halt; // release:停机

核心思想

1. 一条根本保证

健全性:无论怎样,Safe Rust 都不可能导致未定义行为。

unsafe 的意义不是"逃离类型系统",而是在类型系统之下构建新的安全抽象,把安全向上托举给 Safe Rust。

2. 一组不对称的信任关系

  • Safe Rust 无条件信任接触到的 Unsafe Rust;
  • Unsafe Rust 不能信任泛型 Safe 代码(BTreeMap 不信任用户的 Ord);
  • 限制 unsafe 影响范围的唯一可靠边界 = 模块 + 私有性

3. 一份 UB 清单与一套纪律

1
2
3
4
5
6
每处 unsafe 都要能回答:
① 我依赖什么契约?(索引在界内?指针有效?已初始化?)
② 契约由谁保证?(上方检查?模块不变式?调用者承诺?)
③ panic 路径上契约还成立吗?
④ 被 mem::forget 后果是什么?(答案必须是"泄漏"而非 UB)
回答不出来 = 不要写。

4. 一套底层工具词汇表

工具 一句话用途
ptr::write / ptr::read 初始化 / 移出(绕过赋值的"读旧值+drop")
ptr::copy memmove(重叠区间安全,参数序与 C 相反)
mem::forget / ManuallyDrop 阻止析构、防双重释放
Option::take 从字段中安全移出值(Drop 解法标配)
MaybeUninit 未初始化内存的唯一正确处理
NonNull + PhantomData 裸指针容器标配:variance + dropck + auto trait
#[repr(C)] / #[repr(transparent)] 唯一可依赖的布局保证
Ordering SeqCst 保底 / 锁 Acquire-Release / 计数 Relaxed
NonNull::dangling() ZST 与"未分配"的合法占位指针

5. 一场实战收束(第 9-10 章串起全书)

1
2
3
4
5
6
7
8
手写 Vec/Arc 时用到的知识点映射:
布局(§2) → NonNull + dangling + isize::MAX 限制
所有权(§3) → PhantomData 声明借用语义
未初始化(§5) → ptr::write 初始化 / set_len 时机
RAII(§6) → 递归 drop、防双重释放三件套、leak amplification
异常安全(§7) → panic 路径不暴露未初始化内存
并发(§8) → Relaxed clone + Release/Acquire drop、计数回绕 abort
读懂这两个实现,unsafe Rust 的核心纪律就基本掌握了

附:术语中英对照速查

英文 中文
soundness 健全性
Undefined Behavior (UB) 未定义行为
aliasing 别名
variance (covariant/contravariant/invariant) 可变性(协变/逆变/不变)
drop check (dropck) Drop 检查
unwinding 栈展开
exception safety 异常安全
poisoning 中毒
data race / race condition 数据竞争 / 竞态条件
dangling pointer 悬垂指针
niche / null pointer optimization 空位 / 空指针优化
DST / ZST 动态 / 零大小类型
OBRM / RAII 基于所有权的资源管理
leak amplification 泄漏放大
GEP (getelementptr) LLVM 指针偏移指令
HRTB 高阶 trait 约束
opaque struct 不透明结构体


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!