Unsafe Rust 与 C/C++ FFI 交互实战
深入掌握 Unsafe Rust:5 大超能力、裸指针解引用、C 语言 FFI 双向互操作以及构建健全的安全抽象层。
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);
}
}rust2. 调用 Unsafe 函数与构建安全抽象层#
Rust 社区推崇的经典模式是:将 Unsafe 限制在最小的代码核心内,并向外暴露健全的 Safe API(安全抽象)。
以标准库的 split_at_mut 为例(将一个可变切片拆分为互不重叠的两个子切片):
src/main.rs
use std::slice;
fn split_at_mut<T>(values: &mut [T], mid: usize) -> (&mut [T], &mut [T]) {
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 vector = vec![1, 2, 3, 4, 5, 6];
let (left, right) = split_at_mut(&mut vector, 3);
println!("Left: {left:?}, Right: {right:?}");
}rust3. 跨语言调用: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 编码黄金准则#
- 最小化 Unsafe 作用域:
unsafe代码块应保持简短精炼。 - 记录不变式(Invariants):在代码注释中明确解释为何该操作在语义上是健全的。
- 善用 Miri 工具:使用
cargo miri test在测试环境中检测未定义行为(Undefined Behavior, UB)与内存泄漏。
总结#
unsafe解锁了进行系统底层开发、硬件交互和跨语言互操作所必需的 5 种能力。- 裸指针(
*const T,*mut T)提供了不受借用检查器约束的原始内存访问手段。 - FFI 为 Rust 与现存海量 C/C++ 基础设施架起了零开销互操作的桥梁。
- 坚持使用安全包装(Safe Abstraction)封装不安全逻辑是写出高质量生产代码的核心基石。