高级主题与源码阅读

本章目标: 学习源码阅读方法论,掌握 FFI、性能优化,成为 Rust 高手

学习时间: 7-10 天

前置知识: 完成前 9 个章节


1. 源码阅读方法论

为什么要读源码?

读源码的收益:
1. 理解"为什么这样设计" (思维层面)
2. 学习惯用法和模式 (实践层面)
3. 加深对语言特性的理解 (技术层面)
4. 贡献开源 (社区层面)

不要读源码的误区:
- 一开始就逐行读 (应该先了解大局)
- 不带问题读 (应该带着问题找答案)
- 不运行代码 (理解要结合实践)

5步源码阅读法

Step 1: 了解目的
  - 这个库/模块是做什么的?
  - 用几句话描述其核心职责

Step 2: 看接口 (API)
  - 对外暴露了什么类型?
  - 有哪些公开函数/方法?
  - 不看实现, 只看签名

Step 3: 看测试
  - tests/ 目录下有什么?
  - 集成测试揭示了正常使用场景
  - 属性测试揭示了边界条件

Step 4: 从入口追踪
  - new() / init() / 创建类型的地方
  - 从简单的路径开始

Step 5: 带问题深挖
  - "这个字段为什么是 Option<T>?"
  - "这里为什么用 unsafe?"
  - "为什么不用 X 而用 Y?"

2. 推荐源码阅读: std 库

阅读 Vec<T>

文件位置: library/alloc/src/vec/mod.rs

// 1. 看结构体
pub struct Vec<T, #[unstable(...)] A: Allocator = Global> {
    buf: RawVec<T, A>,
    len: usize,
}
 
// 2. RawVec 是什么?
// library/alloc/src/raw_vec.rs
pub(crate) struct RawVec<T, A: Allocator = Global> {
    ptr: Unique<T>,
    cap: usize,
    alloc: A,
}
 
// 关键洞察:
// - Vec 分离了"持有内存"(RawVec) 和"实际元素个数"(len)
// - 这是容量/长度分离的设计

学习重点:

  1. push() 如何检测并扩容 (grow() 或 reserve_for_push())
  2. pop() 如何正确处理 drop
  3. drain() 的实现中 SetLenOnDrop guard 的用途
  4. split_at_spare_mut() unsafe 实现

阅读 HashMap

关键数据结构:

// hashbrown::HashMap 是 std::HashMap 的后端
// 使用 Swiss Tables (Robin Hood with SIMD lookup)
 
pub struct HashMap<K, V, S = RandomState> {
    base: base::HashMap<K, V, S>,
}
 
// Probe 序列:
// 1. Hash key -> index h1
// 2. h2 (7-bit hash) 存在控制字节中
// 3. SIMD 并行比较 16 个控制字节
// 4. 找到匹配的槽位, 比较完整 key

学习重点:

  1. 什么是开放寻址法的探测序列
  2. 控制字节 (control bytes) 的设计
  3. entry() API 如何避免二次哈希

3. 推荐源码阅读: tokio

Tokio 运行时架构

Tokio Runtime:
┌─────────────────────────────────────────┐
│              RuntimeHandle               │
├──────────┬──────────────────────────────┤
│  Scheduler│  IODriver  │  TimeDriver     │
│  (多线程) │  (epoll)   │  (时间轮)       │
├──────────┴──────────────────────────────┤
│              BlockingPool                │
└─────────────────────────────────────────┘

推荐阅读路径:

  1. tokio/src/runtime/mod.rs - 入口
  2. tokio/src/runtime/scheduler/ - 任务调度
  3. tokio/src/io/driver/mod.rs - IO 事件循环
  4. tokio/src/time/driver/mod.rs - 时间轮算法

关键问题:

  • work_stealing 如何实现任务窃取?
  • I/O 事件如何触发 Future 的唤醒?
  • rt::spawn 和 rt::spawn_blocking 有什么区别?

4. FFI: 与 C 互操作

调用 C 函数

// 声明 C 函数
extern "C" {
    fn strlen(s: *const std::os::raw::c_char) -> usize;
    fn malloc(size: usize) -> *mut std::os::raw::c_void;
    fn free(ptr: *mut std::os::raw::c_void);
    fn printf(fmt: *const std::os::raw::c_char, ...) -> i32;
}
 
use std::ffi::CString;
 
fn call_c_strlen() {
    let s = CString::new("hello").unwrap();
    let len = unsafe { strlen(s.as_ptr()) };
    println!("strlen = {}", len);  // 5
}
 
// 动态链接库
#[link(name = "m")]  // libm
extern "C" {
    fn sin(x: f64) -> f64;
    fn cos(x: f64) -> f64;
}
 
fn trig() {
    let pi = std::f64::consts::PI;
    let sin_val = unsafe { sin(pi / 6.0) };
    println!("sin(30°) = {}", sin_val);  // ≈ 0.5
}

提供 C 接口 (Rust 被 C 调用)

// 声明 C-compatible 函数
#[no_mangle]  // 不混淆函数名
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
    a + b
}
 
// 对应的 C 头文件 (手动编写或 cbindgen 生成):
// int32_t rust_add(int32_t a, int32_t b);
 
// Cargo.toml 需要:
// [lib]
// crate-type = ["cdylib"]  # 动态库
// 或 ["staticlib"]         # 静态库

处理字符串

use std::ffi::{CStr, CString};
use std::os::raw::c_char;
 
// Rust -> C
fn rust_to_c_string() -> *mut c_char {
    let s = CString::new("Hello from Rust").unwrap();
    s.into_raw()  // 转移所有权给 C
}
 
// C -> Rust (借用)
unsafe fn c_to_rust_str(ptr: *const c_char) -> &'static str {
    CStr::from_ptr(ptr)
        .to_str()
        .expect("invalid UTF-8")
}
 
// 释放 Rust 分配的字符串 (给 C 调用)
#[no_mangle]
pub extern "C" fn free_rust_string(ptr: *mut c_char) {
    if !ptr.is_null() {
        unsafe { drop(CString::from_raw(ptr)); }
    }
}

处理结构体

// 与 C 兼容的结构体
#[repr(C)]  // 使用 C 的内存布局
pub struct Point {
    pub x: f64,
    pub y: f64,
}
 
#[repr(C)]
pub struct Array {
    pub data: *const f64,
    pub len: usize,
}
 
#[no_mangle]
pub extern "C" fn compute_distance(a: Point, b: Point) -> f64 {
    let dx = a.x - b.x;
    let dy = a.y - b.y;
    (dx * dx + dy * dy).sqrt()
}

使用 bindgen 自动生成绑定

# 安装
cargo install bindgen-cli
 
# 生成绑定
bindgen /usr/include/sqlite3.h -o src/bindings.rs
 
# 或在 build.rs 中自动生成
// build.rs
fn main() {
    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .generate()
        .expect("Unable to generate bindings");
    
    bindings
        .write_to_file(std::path::PathBuf::from("src/bindings.rs"))
        .expect("Couldn't write bindings!");
    
    println!("cargo:rerun-if-changed=wrapper.h");
    println!("cargo:rustc-link-lib=sqlite3");
}

5. 性能优化

测量优先

// 使用 criterion 进行基准测试
// Cargo.toml:
// [dev-dependencies]
// criterion = { version = "0.5", features = ["html_reports"] }
//
// [[bench]]
// name = "my_benchmark"
// harness = false
 
// benches/my_benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
 
fn fibonacci(n: u64) -> u64 {
    match n {
        0 => 0,
        1 => 1,
        n => fibonacci(n - 1) + fibonacci(n - 2),
    }
}
 
fn bench_fibonacci(c: &mut Criterion) {
    c.bench_function("fib 20", |b| {
        b.iter(|| fibonacci(black_box(20)))
    });
}
 
criterion_group!(benches, bench_fibonacci);
criterion_main!(benches);

常见优化技巧

// 1. 避免不必要的克隆
fn bad(s: String) -> usize { s.len() }   // 需要传入所有权
fn good(s: &str) -> usize { s.len() }     // 借用即可
 
// 2. 使用迭代器而非索引
fn bad_sum(v: &[i32]) -> i32 {
    let mut sum = 0;
    for i in 0..v.len() { sum += v[i]; }  // 有边界检查
    sum
}
 
fn good_sum(v: &[i32]) -> i32 {
    v.iter().sum()  // 编译器可以优化
}
 
// 3. 预分配容量
fn bad_collect(n: usize) -> Vec<i32> {
    let mut v = Vec::new();  // 多次重分配
    for i in 0..n { v.push(i as i32); }
    v
}
 
fn good_collect(n: usize) -> Vec<i32> {
    let mut v = Vec::with_capacity(n);  // 一次分配
    for i in 0..n { v.push(i as i32); }
    v
}
 
// 4. 缓存友好的数据布局
// 差: Vec<Box<T>> (指针追逐)
// 好: Vec<T>      (连续内存)

并行加速

use rayon::prelude::*;
 
fn parallel_sum(data: &[i64]) -> i64 {
    data.par_iter().sum()  // 自动并行!
}
 
fn parallel_sort(mut data: Vec<i64>) -> Vec<i64> {
    data.par_sort_unstable();
    data
}
 
fn parallel_map(data: &[i32]) -> Vec<i32> {
    data.par_iter()
        .map(|&x| x * x)
        .filter(|&x| x > 100)
        .collect()
}

SIMD 优化 (进阶)

// 可移植 SIMD (Rust nightly)
#![feature(portable_simd)]
use std::simd::f32x8;
 
fn simd_add(a: &[f32], b: &[f32], out: &mut [f32]) {
    let chunks_a = a.chunks_exact(8);
    let chunks_b = b.chunks_exact(8);
    let chunks_out = out.chunks_exact_mut(8);
    
    for ((ca, cb), co) in chunks_a.zip(chunks_b).zip(chunks_out) {
        let va = f32x8::from_slice(ca);
        let vb = f32x8::from_slice(cb);
        (va + vb).copy_to_slice(co);
    }
    
    // 处理剩余元素
    let remainder = a.len() - a.len() % 8;
    for i in remainder..a.len() {
        out[i] = a[i] + b[i];
    }
}

6. 编译优化配置

# Cargo.toml
[profile.release]
opt-level = 3        # 最高优化 (0-3)
lto = "thin"         # 链接时优化 (thin/fat/true)
codegen-units = 1    # 单编译单元, 更好优化但更慢编译
panic = "abort"      # 将 panic 变为 abort (减少体积)
strip = true         # 去除符号表
 
[profile.bench]
inherits = "release"
debug = true         # 保留调试信息用于性能分析
# 检查生成的汇编
cargo rustc --release -- --emit=asm
# 使用 cargo-asm
cargo install cargo-asm
cargo asm my_crate::my_function
 
# 使用 perf 分析
cargo build --release
perf record ./target/release/my_program
perf report

7. 开源贡献指南

寻找合适的项目

beginner 友好的项目:
- rustlings: 学习练习
- ripgrep: 命令行工具
- serde: 序列化框架
- tokio: 异步运行时

寻找 Issues 的方法:
- GitHub tag: "good first issue"
- GitHub tag: "help wanted"
- "E-easy" in Rust compiler issues

贡献流程

# 1. Fork 项目
# 2. Clone
git clone https://github.com/你的用户名/项目名
 
# 3. 创建分支
git checkout -b fix/issue-number-description
 
# 4. 开发 + 测试
cargo test
cargo clippy
cargo fmt
 
# 5. 提交
git commit -m "Fix: 简述修改"
 
# 6. Push
git push origin fix/issue-number-description
 
# 7. 创建 PR
# 在 GitHub 上创建 Pull Request

8. Rust 编译器内部 (概览)

Rust 编译过程:
源码(.rs)
  ↓ 词法分析
Token流
  ↓ 语法分析
AST (抽象语法树)
  ↓ 宏展开 + 名称解析
HIR (高级中间表示)
  ↓ 类型检查 + 借用检查
MIR (中间表示)
  ↓ 优化
LLVM IR
  ↓ LLVM 后端
机器码

在哪里看借用检查:

  • compiler/rustc_borrowck/ - 借用检查器
  • compiler/rustc_typeck/ - 类型检查

推荐阅读的开源项目

项目学习重点
ripgrep命令行工具设计, 并行搜索
tokio异步运行时内部实现
serde过程宏的实际应用
rayonWork-Stealing 并行
axum类型驱动的 Web 框架
sqlx编译时 SQL 检查
nomParser Combinator
crossbeam并发数据结构

小结: 成为 Rust 高手的路径

Level 1: 会写 (语法正确)
Level 2: 会用 (惯用法)
Level 3: 会设计 (架构)
Level 4: 会优化 (性能)
Level 5: 会贡献 (开源)
Level 6: 会创造 (生态)

完成本教程后, 你应该在 Level 3-4 之间

持续提升

  • 坚持读一个开源项目的源码 (每周 1-2 小时)
  • 参与 Rust 社区讨论 (IRLO, Users Forum)
  • 完成 Rustlings 的所有练习
  • 尝试贡献一个 good-first-issue
  • 写技术博客分享理解

恭喜完成全部 10 个核心章节! 你已经是真正的 Rustacean! 🦀