本章导读
到目前为止,我们一直生活在 Rust 编译器精心构建的“温室”里。借用检查器保护我们免受内存错误的伤害,线程安全检查让我们远离数据竞争。
但是,真实的世界往往没那么完美。
有时候,你需要调用一个几十年前写的 C 语言库(比如 OpenSSL 或 FFmpeg);有时候,你需要手动管理一块裸内存以获得极致性能;更多的时候(对于在座的 Python 开发者),你渴望把 Python 代码中那个慢得像蜗牛的循环,用 Rust 重写,然后像导入普通模块一样在 Python 中调用它。
这一章,我们要学会“越狱”。我们将使用
unsafe关键字关闭编译器的部分安全检查,通过 FFI (外部函数接口) 与 C 语言对话,并使用 PyO3 让 Python 能够直接“寄生”在 Rust 的高性能之上。别担心,
unsafe不代表“不安全”,它代表“开发者承诺对安全负责”。
unsafe 到底关闭了哪些检查,以及为什么说“安全抽象建立在不安全之上”。裸指针操作:学会解引用裸指针(Raw Pointers),理解它与引用的区别。调用 C 语言:通过 FFI 调用系统的
libc 函数,体验跨语言交互。Python 加速:这是本章的高潮。使用 PyO3 和 Maturin 构建一个 Python 扩展模块,实现“Python 的语法,C 的性能”。工程化思维:如何封装 Unsafe 代码,使其对外提供 Safe 的接口。
Java 开发者可能听说过
sun.misc.Unsafe,那是一个能直接操作内存的后门。Python 开发者在写 C 扩展时,其实全程都在裸奔。
在 Rust 中,
unsafe 也是类似的机制。当你打出
unsafe { ... } 时,你是在告诉编译器:“请相信我,我知道我在做什么,出了问题我负责,别报错。”
unsafe 并不是让你可以为所欲为,它只赋予你五个特权:
unsafe 函数或方法。访问或修改可变的静态变量(Global Static)。实现
unsafe trait。访问
union 的字段。
除此之外,借用检查、所有权规则依然生效!
裸指针(Raw Pointer,
*const T 和
*mut T)类似于 C 语言的指针。它们可以为空,可以悬垂,不受 Rust 生命周期约束。
fn main() {
let mut num = 5;
// 创建裸指针是 Safe 的,不需要 unsafe 块
// 类似于 Java 中获取对象的内存地址(虽然 Java 不让你直接这么干)
let r1 = &num as *const i32; // 不可变裸指针
let r2 = &mut num as *mut i32; // 可变裸指针
// ❌ 下面这行如果不加 unsafe 块,编译会报错
// println!("r1 points to: {}", *r1);
unsafe {
// ✅ 解引用裸指针必须在 unsafe 块中
println!("r1 points to: {}", *r1);
println!("r2 points to: {}", *r2);
// 修改内存值
*r2 = 10;
}
println!("num is now: {}", num); // 输出 10
}
⚙️ 老司机提示 (Veteran’s Tip)
既然裸指针这么危险,为什么还要用?
与 C 交互:C 语言的接口全是裸指针。构建基础数据结构:如果你去读Vec或HashMap的源码,你会发现它们底层全是unsafe。因为有些性能极高的内存布局(如环形缓冲区),编译器无法自动验证其安全性,必须人工保证。
Rust 的目标不是替代 C,而是与 C 共存。通过
extern 块,我们可以声明外部函数。
让我们尝试调用 C 语言标准库中的
abs(绝对值)和
system(执行 Shell 命令)函数。
// 声明外部函数接口
extern "C" {
fn abs(input: i32) -> i32;
fn system(command: *const u8) -> i32;
}
fn main() {
unsafe {
// 1. 调用 abs
let res = abs(-3);
println!("C language absolute value: {}", res);
// 2. 调用 system
// 注意:C 字符串必须以 结尾
let cmd = "echo 'Hello from C Shell!'