blog.dopana

Back

Part 3:高级系统编程与生产级 Rust 实战 第 2 篇。尽管 Rust 以严苛的静态内存安全闻名于世,但在真实的底层系统开发场景中 —— 操作系统内核、硬件驱动、调用遗留的 C/C++ 库或追求极致吞吐量时,计算机必须执行一些编译器在静态层面无法证明其绝对安全的操作。

Rust 通过 unsafe 关键字为这类底层需求提供了合法的通道。

graph TD
    A["Unsafe Rust"] --> B["5 大 Unsafe 超能力"]
    B --> B1["解引用裸指针 (*const T, *mut T)"]
    B --> B2["调用 unsafe 函数或外部 FFI 函数"]
    B --> B3["实现 Unsafe 特征"]
    B --> B4["访问或修改可变静态变量 (static mut)"]
    B --> B5["访问联合体 (union) 的字段"]
    A --> C["C 语言 FFI 双向调用"]
    A --> D["核心模式:Safe 抽象封装 Unsafe 内核"]

浅显易懂(ELI5):高压电工的防护箱#

  • Safe Rust:就像家里的电灯开关,绝缘保护完善,任何人按下开关都是 100% 安全的。
  • Unsafe Rust:就像打开了变电箱的检修盖并脱下了绝缘手套。编译器此时会提醒你:“我无法在静态阶段检查这里的线路是否带电,控制权和安全性现在完全由你负责!”
  • unsafe 并没有关闭借用检查器,也没有让 Rust 倒退为弱类型语言;它只是精确地赋予了你 5 种底层操作权限。

1. 裸指针(Raw Pointers: *const T*mut T#

与常规引用(&T&mut T)相比,裸指针具有以下特点:

  • 允许忽略借用规则(可以有多个可变指针指向同一内存地址)。
  • 不保证指向有效内存(可能是 null 或悬垂指针)。
  • 不会自动调用析构逻辑(无自动 Drop)。

[!NOTE] 创建裸指针是完全安全(Safe)的。只有在解引用(*pointer)读写内存数据时,才必须放入 unsafe 块中。

src/main.rs
fn main() {
    let mut num = 42;

    // 从引用创建裸指针(Safe)
    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    // 解引用裸指针(Unsafe)
    unsafe {
        println!("r1 指向的值: {}", *r1);
        *r2 = 100;
        println!("r2 修改后的值: {}", *r2);
    }
}
rust

2. 调用 Unsafe 函数与构建安全抽象层#

Rust 社区推崇的经典模式是:将 Unsafe 限制在最小的代码核心内,并向外暴露健全的 Safe API(安全抽象)。

以标准库的 split_at_mut 为例(将一个可变切片拆分为互不重叠的两个子切片):

3. 跨语言调用:C 外部函数接口(FFI)#

Rust 遵循标准 C ABI,可以与 C/C++ 库实现零开销双向交互:

调用 C 标准库函数#

src/main.rs
use std::ffi::c_int;

// 声明外部 C 符号
extern "C" {
    fn abs(input: c_int) -> c_int;
}

fn main() {
    unsafe {
        let result = abs(-42);
        println!("C 标准库 abs 输出: {result}");
    }
}
rust

将 Rust 函数导出给 C/C++ 使用#

你可以将 Rust 模块编译为动态链接库(.so, .dylib, .dll)供 C、Python、Go 等语言无缝调用:

#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
    a + b
}
rust
  • #[no_mangle]:禁用编译器的符号名称重命名规则,方便 C 链接器定位函数。
  • extern "C":指定遵循标准 C 的函数调用约定。

Unsafe 编码黄金准则#

  1. 最小化 Unsafe 作用域:unsafe 代码块应保持简短精炼。
  2. 记录不变式(Invariants):在代码注释中明确解释为何该操作在语义上是健全的。
  3. 善用 Miri 工具:使用 cargo miri test 在测试环境中检测未定义行为(Undefined Behavior, UB)与内存泄漏。

总结#

  • unsafe 解锁了进行系统底层开发、硬件交互和跨语言互操作所必需的 5 种能力。
  • 裸指针(*const T, *mut T)提供了不受借用检查器约束的原始内存访问手段。
  • FFI 为 Rust 与现存海量 C/C++ 基础设施架起了零开销互操作的桥梁。
  • 坚持使用安全包装(Safe Abstraction)封装不安全逻辑是写出高质量生产代码的核心基石。

参考资料#