系统编程

第20课:unsafe Rust

🎯 本课目标

理解unsafe Rust的作用、五大致命能力、安全抽象和FFI基础。

1. unsafe的五种能力

⚠️ unsafe不关闭借用检查!它只提供五种额外能力:
  1. 解引用原始指针
  2. 调用unsafe函数/方法
  3. 访问/修改可变静态变量
  4. 实现unsafe trait
  5. 访问union字段

2. 原始指针

fn main() {
    let mut num = 5;

    // 创建原始指针(safe)
    let r1 = &num as *const i32;    // 不可变原始指针
    let r2 = &mut num as *mut i32;  // 可变原始指针

    // 解引用(unsafe!)
    unsafe {
        println!("r1 = {}", *r1);
        *r2 = 10;
        println!("r2 = {}", *r2);
    }
}

✅ 编译验证通过

3. unsafe函数

// unsafe fn — 调用者需保证安全条件
unsafe fn dangerous() {
    println!("做危险的事...");
}

fn main() {
    unsafe {
        dangerous();
    }
}

✅ 编译验证通过

4. 安全抽象

use std::slice;

// split_at_mut — 用unsafe实现安全接口
fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = values.len();
    let ptr = values.as_mut_ptr();

    assert!(mid <= len);

    unsafe {
        (
            slice::from_raw_parts_mut(ptr, mid),
            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

fn main() {
    let mut v = [1, 2, 3, 4, 5, 6];
    let (left, right) = split_at_mut(&mut v, 3);
    println!("left={:?} right={:?}", left, right);
}

✅ 编译验证通过

5. 可变静态变量

static mut COUNTER: u32 = 0;

fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    add_to_count(3);
    unsafe {
        println!("COUNTER: {}", COUNTER);
    }
}

✅ 编译验证通过

unsafe的安全边界: ┌─────────────────────────────┐ │ Safe Rust (编译器保证) │ │ ┌─────────────────────┐ │ │ │ unsafe块 (程序员保证) │ │ │ │ · 原始指针解引用 │ │ │ │ · unsafe fn调用 │ │ │ │ · 静态变量修改 │ │ │ └─────────────────────┘ │ │ │ │ 外部代码仍享受安全保证 │ └─────────────────────────────┘ 关键原则:unsafe应该被封装在安全接口内!

📝 练习题

练习1:自定义Vec

用原始指针实现一个简化版Vec<i32>。

练习2:链表节点

用原始指针实现双向链表节点。

练习3:安全审查

审查一段unsafe代码,找出潜在的未定义行为。

🏆

成就解锁:黑暗骑士

掌握unsafe——在安全边界内驾驭原始力量

🔬 深度理解:Rust与系统编程

系统编程的核心挑战是在不牺牲安全的前提下获得最大性能。Rust如何实现这个目标:

1. 无GC的内存管理

所有权系统在编译期确定每个值的生命周期,不需要运行时垃圾回收。这意味着:无停顿、可预测延迟、更少内存占用。

2. 零成本抽象到硬件

泛型单态化、内联、LLVM优化——高级代码编译为与手写C相当的机器码。

3. 并发无数据竞争

Send/Sync trait在编译期保证:如果是并发访问,要么只读,要么互斥。数据竞争不可能发生。

📊 Rust vs C/C++对比

特性RustCC++
内存安全✅ 编译期❌ 手动⚠️ 部分RAII
线程安全✅ 编译期
无GC
零成本抽象⚠️ 宏✅ 模板
包管理✅ Cargo⚠️ vcpkg
学习曲线陡峭中等极陡

🛡️ 安全编码实践

即使Rust保证内存安全和线程安全,仍需注意:

🧪 实战练习补充

以下练习帮助你将本课知识应用到实际场景:

练习A:代码审查

阅读以下代码片段,找出潜在问题并改进:

// 仔细阅读,思考有什么问题
fn process(data: Vec) -> Vec {
    let mut result = Vec::new();
    for i in 0..data.len() {
        result.push(data[i] * 2);
    }
    result
}

改进建议:使用迭代器、预分配容量、考虑溢出。

练习B:性能对比

用criterion基准测试对比以下实现的性能差异:

  • for循环 vs 迭代器
  • String拼接 vs write!宏
  • Vec::push vs Vec::with_capacity

练习C:API设计

为以下场景设计Rust API:一个支持过期时间的缓存系统。考虑所有权、错误处理和泛型。

📌 本课要点总结

通过本课的学习,你应该掌握以下核心要点:

  1. 理解Rust核心概念背后的设计哲学
  2. 能够将所学知识应用到实际编码中
  3. 知道何时选择哪种工具和模式
  4. 具备阅读和审查Rust代码的能力
  5. 能够独立解决编译器报告的问题

如果某个要点还不够清晰,回顾本课的代码示例和内存图,动手编写代码验证你的理解。

🔗 相关资源

Rust系统编程课程 · 第20课 · 2026

📚 扩展阅读

以下资源帮助你深入学习本课主题:

  • The Rust Programming Language — 官方教程,Rust圣经
  • Rust by Example — 通过示例学习Rust
  • Rust Reference — 语言参考手册
  • Rustnomicon — Unsafe Rust黑魔法指南
  • Too Many Lists — 用Rust实现各种链表

🔑 关键术语回顾

本课涉及的核心概念,确保你理解每一个:

术语说明
所有权Rust内存管理的核心机制,每个值有唯一所有者
借用通过引用访问数据,不获取所有权
生命周期引用有效的范围,编译期分析工具
traitRust的接口/抽象机制,类似其他语言的接口
泛型参数化类型,零成本抽象
模式匹配强大的数据解构和分支机制
零成本抽象高层抽象不引入运行时开销
fearless concurrency编译器保证线程安全

💬 学习建议

  1. 动手编码 — 每个代码示例都在本地运行一遍
  2. 修改实验 — 故意改错代码,看编译器报什么错
  3. 完成练习 — 每课的练习题是巩固知识的关键
  4. 阅读源码 — 看标准库和优秀开源项目的实现
  5. 写项目 — 真正掌握需要构建真实项目

🤔 常见问题

Q: Rust学习曲线真的很陡吗?

A: 前期确实需要适应所有权和借用检查器,但一旦理解了,这些概念会让你的代码更可靠。大多数人2-4周就能上手。

Q: Rust适合什么项目?

A: 系统编程、Web服务、CLI工具、嵌入式、WASM、网络服务、数据库等。基本上需要性能和安全的地方都适合。

Q: 遇到编译错误怎么办?

A: Rust编译器的错误信息非常友好!仔细阅读,通常会指出问题所在和修复建议。也可以用cargo clippy获取更多提示。