《Rust 实战指南》第 11 章:越界——Unsafe Rust 与混合编程实战

  • 时间:2025-12-01 21:39 作者: 来源: 阅读:3
  • 扫一扫,手机访问
摘要: 本章导读 到目前为止,我们一直生活在 Rust 编译器精心构建的“温室”里。借用检查器保护我们免受内存错误的伤害,线程安全检查让我们远离数据竞争。 但是,真实的世界往往没那么完美。 有时候,你需要调用一个几十年前写的 C 语言库(比如 OpenSSL 或 FFmpeg);有时候,你需要手动管理一块裸内存以获得极致性能;更多的时候(对于在座的 Python 开发者),你渴望把 Python 代码中

本章导读

到目前为止,我们一直生活在 Rust 编译器精心构建的“温室”里。借用检查器保护我们免受内存错误的伤害,线程安全检查让我们远离数据竞争。

但是,真实的世界往往没那么完美。

有时候,你需要调用一个几十年前写的 C 语言库(比如 OpenSSL 或 FFmpeg);有时候,你需要手动管理一块裸内存以获得极致性能;更多的时候(对于在座的 Python 开发者),你渴望把 Python 代码中那个慢得像蜗牛的循环,用 Rust 重写,然后像导入普通模块一样在 Python 中调用它。

这一章,我们要学会“越狱”。我们将使用 unsafe 关键字关闭编译器的部分安全检查,通过 FFI (外部函数接口) 与 C 语言对话,并使用 PyO3 让 Python 能够直接“寄生”在 Rust 的高性能之上。

别担心, unsafe 不代表“不安全”,它代表“开发者承诺对安全负责”。


🎯 本章学习目标

理解 Unsafe:知道 unsafe 到底关闭了哪些检查,以及为什么说“安全抽象建立在不安全之上”。裸指针操作:学会解引用裸指针(Raw Pointers),理解它与引用的区别。调用 C 语言:通过 FFI 调用系统的 libc 函数,体验跨语言交互。Python 加速:这是本章的高潮。使用 PyO3Maturin 构建一个 Python 扩展模块,实现“Python 的语法,C 的性能”。工程化思维:如何封装 Unsafe 代码,使其对外提供 Safe 的接口。

11.1 揭开 Unsafe 的面纱

Java 开发者可能听说过 sun.misc.Unsafe,那是一个能直接操作内存的后门。Python 开发者在写 C 扩展时,其实全程都在裸奔。

在 Rust 中, unsafe 也是类似的机制。当你打出 unsafe { ... } 时,你是在告诉编译器:“请相信我,我知道我在做什么,出了问题我负责,别报错。”

11.1.1 Unsafe 只能做五件事

unsafe 并不是让你可以为所欲为,它只赋予你五个特权:

解引用裸指针。调用 unsafe 函数或方法。访问或修改可变的静态变量(Global Static)。实现 unsafe trait。访问 union 的字段。

除此之外,借用检查、所有权规则依然生效!

11.1.2 实战:解引用裸指针

裸指针(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。因为有些性能极高的内存布局(如环形缓冲区),编译器无法自动验证其安全性,必须人工保证。

11.2 FFI:与 C 语言的老朋友对话

Rust 的目标不是替代 C,而是与 C 共存。通过 extern 块,我们可以声明外部函数。

11.2.1 调用标准 C 库 (libc)

让我们尝试调用 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!'"; 
        system(cmd.as_ptr());
    }
}

11.2.2 安全封装(Safe Wrapper)

在工程实践中,我们不会让 unsafe 代码散落在业务逻辑里。我们会编写一个安全包装层


// 这是一个 Safe 函数,内部处理了 Unsafe 逻辑
fn run_shell_command(cmd: &str) -> i32 {
    use std::ffi::CString;
    
    // 处理 Rust 字符串到 C 字符串的转换(处理  问题)
    let c_cmd = CString::new(cmd).expect("CString::new failed");
    
    unsafe {
        // 调用外部函数
        // system 是 unsafe 的,但我们通过封装保证了传入的指针是合法的
        extern "C" { fn system(c: *const i8) -> i32; }
        system(c_cmd.as_ptr())
    }
}

fn main() {
    // 现在调用起来非常安全,不需要 unsafe 块
    run_shell_command("ls -l");
}

11.3 实战案例:PyO3 —— 让 Python 起飞

对于 Python 开发者来说,这是本书最具价值的一节。
Python 的痛点是慢,Rust 的强项是快。通过 PyO3 库,你可以用 Rust 编写 Python 模块,性能提升通常在 10倍 到 100倍 之间。

我们将构建一个项目:计算斐波那契数列。对比 Python 原生版和 Rust 加速版。

11.3.1 环境准备

你需要安装 maturin,这是一个构建 Rust 扩展并发布到 PyPI 的工具(替代 setup.py)。


pip install maturin

11.3.2 初始化项目


# 创建一个新的库项目
maturin new --lib rusty_fib
cd rusty_fib

maturin 会自动生成 Cargo.toml pyproject.toml。此时你的 Cargo.toml 应该包含 pyo3 依赖。


[lib]
name = "rusty_fib"
crate-type = ["cdylib"] # 关键:编译成动态链接库 (.so / .pyd)

[dependencies]
pyo3 = { version = "0.20", features = ["extension-module"] }

11.3.3 编写 Rust 代码 (src/lib.rs)


use pyo3::prelude::*;

// 1. 定义一个普通的 Rust 函数
// 这里的算法故意用递归,为了凸显 CPU 性能差异
fn fib_rs(n: u64) -> u64 {
    if n <= 2 { 1 } else { fib_rs(n - 1) + fib_rs(n - 2) }
}

// 2. 使用 #[pyfunction] 宏将其暴露给 Python
#[pyfunction]
fn calculate_fib(n: u64) -> PyResult<u64> {
    Ok(fib_rs(n))
}

// 3. 定义 Python 模块
#[pymodule]
fn rusty_fib(_py: Python, m: &PyModule) -> PyResult<()> {
    // 将函数添加到模块中
    m.add_function(wrap_pyfunction!(calculate_fib, m)?)?;
    Ok(())
}

11.3.4 编译并安装

在终端运行:


# 这会将 Rust 代码编译并安装到当前的 Python 环境中
maturin develop --release

一旦完成,你就可以在 Python 里 import rusty_fib 了!


11.4 性能巅峰对决

现在,我们来写一个 Python 脚本 benchmark.py,对比两者的速度。


import time
import rusty_fib  # 导入我们刚写的 Rust 模块

# Python 原生实现
def fib_py(n):
    if n <= 2:
        return 1
    else:
        return fib_py(n - 1) + fib_py(n - 2)

N = 35  # 斐波那契第35位,递归计算量很大

print(f"Computing Fib({N})...")

# 测试 Python
start = time.time()
res_py = fib_py(N)
end = time.time()
print(f"Python Result: {res_py}, Time: {end - start:.4f}s")

# 测试 Rust
start = time.time()
res_rs = rusty_fib.calculate_fib(N)
end = time.time()
print(f"Rust   Result: {res_rs}, Time: {end - start:.4f}s")

11.4.1 运行结果

在我的 M1 MacBook 上,结果如下:


Computing Fib(35)...
Python Result: 9227465, Time: 1.8420s
Rust   Result: 9227465, Time: 0.0281s

分析:Rust 版本比 Python 版本快了大约 65 倍
如果在复杂的矩阵运算或字符串处理场景,配合 Rayon 并行库,性能差距甚至可以拉大到几百倍。这就是为什么像 Pydantic、Polars、Tokenizers 等 Python 顶流库都在用 Rust 重写底层。


11.5 AI 辅助 Unsafe 编程

写 Unsafe 代码和 FFI 是极易出错的(例如 C 语言的内存对齐问题、指针释放问题)。

实战场景
你需要调用一个复杂的 C 结构体,包含嵌套指针,你不知道如何在 Rust 中定义对应的 struct

Prompt 建议

“我有一个 C 语言的结构体定义(附代码)。请帮我生成对应的 Rust #[repr(C)] 结构体定义。并帮我写一段代码,演示如何通过 FFI 传入这个结构体指针。请注意内存对齐和 Padding 问题。”

AI 的价值
AI 非常擅长处理这种机械的“翻译”工作,它可以利用 bindgen 的规则为你生成准确的绑定代码,避免你手动数字节偏移量数到眼花。


11.6 进阶:Java 的救星 —— JNI 的替代者

对于 Java 开发者,Rust 同样可以通过 FFI 生成 .dll .so 文件,供 Java 通过 JNI 调用。
虽然 Java 生态没有像 PyO3 那么丝滑的工具(目前有 jni-rs crate),但逻辑是一样的:

Java 定义 native 方法。Rust 使用 extern "C" 实现该方法,且函数名符合 JNI 命名规范(如 Java_com_example_MyClass_myMethod)。编译 Rust 为动态库。Java System.loadLibrary

相比 C++ 写 JNI,Rust 让你再也不用担心在 Native 层写出 Segfault 导致 JVM 崩溃。


11.7 本章小结

Rust 的强大不仅在于它自身的生态,更在于它能成为其他语言的“超级电池”。

Unsafe:是 Rust 的底层基石,它关闭了安全检查,让你能手动操作内存。它是危险的,但也是必要的。FFI:让 Rust 能继承 C/C++ 五十年的遗产。PyO3:是 Python 开发者的杀手级工具。如果你发现 Python 代码遇到了瓶颈,不要急着换 Go 或 C++,试着把那个热点函数用 Rust 重写。

当你掌握了混合编程,你就拥有了“降维打击”的能力——用 Python 的开发效率,跑出 C 的运行速度。


📝 思考与扩展练习

基础题(Unsafe):Rust 的 String 本质上是一个 Vec<u8>。尝试使用 unsafe String::from_utf8_unchecked 方法,将一个 Vec<u8> 转换为 String。思考:如果字节流不是合法的 UTF-8 会发生什么?(这会破坏 Rust 的类型安全保证)。进阶题(PyO3):修改我们的 rusty_fib 项目。在 Rust 代码中引入 rayon 库,编写一个并行的数组求和函数,暴露给 Python。对比 Python sum() 函数和 Rust 并行求和在处理 1 亿个整数时的性能。工程题(JNI 探索):如果你是 Java 开发者,去搜索 jni-rs crate。尝试写一个最简单的 Hello World,让 Java 调用 Rust 打印一行字。
  • 全部评论(0)
最新发布的资讯信息
【系统环境|】创建一个本地分支(2025-12-03 22:43)
【系统环境|】git 如何删除本地和远程分支?(2025-12-03 22:42)
【系统环境|】2019|阿里11面+EMC+网易+美团面经(2025-12-03 22:42)
【系统环境|】32位单片机定时器入门介绍(2025-12-03 22:42)
【系统环境|】从 10 月 19 日起,GitLab 将对所有免费用户强制实施存储限制(2025-12-03 22:42)
【系统环境|】价值驱动的产品交付-OKR、协作与持续优化实践(2025-12-03 22:42)
【系统环境|】IDEA 强行回滚已提交到Master上的代码(2025-12-03 22:42)
【系统环境|】GitLab 15.1发布,Python notebook图形渲染和SLSA 2级构建工件证明(2025-12-03 22:41)
【系统环境|】AI 代码审查 (Code Review) 清单 v1.0(2025-12-03 22:41)
【系统环境|】构建高效流水线:CI/CD工具如何提升软件交付速度(2025-12-03 22:41)
手机二维码手机访问领取大礼包
返回顶部