Rust 学习笔记 27:FFI 交互 (Foreign Function Interface)
“Rust is what C++ should have been.” (Debatable, but Rust interacts with C perfectly)
FFI 允许 Rust 代码调用其他语言写的函数,或者让其他语言调用 Rust 函数。 由于 C 语言是计算机世界的"通用语" (Lingua Franca),FFI 通常指的就是与 C 语言的交互。
1. 调用 C 函数
要在 Rust 中调用 C 函数,我们需要:
- 使用
extern "C"块声明外部函数签名。 - 在
unsafe块中调用它们(因为编译器无法保证 C 代码的安全性)。
1extern "C" {
2 fn abs(input: i32) -> i32;
3}
4
5fn main() {
6 unsafe {
7 println!("Absolute value of -3 according to C: {}", abs(-3));
8 }
9}
Rust 默认会链接系统的标准 C 库 (libc),所以像 abs, malloc, free 这样的标准函数可以直接用。如果是第三方库,还需要在 build.rs 中配置链接参数。
2. 数据类型转换
C 和 Rust 的类型必须一一对应。Rust 提供了 std::os::raw 和 std::ffi 模块来处理这些转换。
i32<->c_int*const c_char<->CString(Rust -> C) /CStr(C -> Rust)
注意 String 和 CString 的区别:Rust 的 String 不以 \0 结尾并且包含长度信息;C 的字符串以 \0 结尾且不知道长度。
3. 暴露 Rust 函数给 C (Rust as a Library)
如果我们想把 Rust 写成一个动态库 (.so / .dll) 给 C/Python/Node.js 调用:
- 添加
#[no_mangle]:防止 Rust 编译器混淆函数名,保持函数名在符号表中原样显示。 - 添加
extern "C":使用 C 的 ABI(调用约定)。
1#[no_mangle]
2pub extern "C" fn call_from_c() {
3 println!("Just called a Rust function from C!");
4}
在 Cargo.toml 中设置 crate-type = ["cdylib"] 即可编译出动态库。
4. Bindgen
手动写 extern "C" 声明对于大库来说太累了。
Rust 社区提供了 bindgen 工具,它可以自动读取 C 头文件 (.h) 并生成对应的 Rust FFI 代码。反过来,cbindgen 可以从 Rust 代码生成 C 头文件。
5. 小结
第27篇笔记。
Rust 的 FFI 能力是它能逐步重写现有 C/C++ 项目的关键。
使用 unsafe 是必须的,但我们可以把 unsafe 的 FFI 调用封装在安全的 Rust API 后面(就像标准库里的 File, TcpStream 底层都是 FFI 调用)。
下一篇,我们将进入一个全新的领域:Web 开发初探 (Web Development)。Rust 能用来写网页后端吗?Actix-web? Axum? 上手写个 Hello World 试试。
练习题:
- 尝试编写一个简单的 C 程序(如
hello.c),编译成对象文件 (.o),然后使用build.rs指示 cargo 链接它,并在 Rust 中调用 C 程序中的函数。 - 了解一下
libccrate,它是 Rust 对 C 标准库的封装,省去了我们自己写extern声明的麻烦。
思考题:
如果 C 函数分配了内存并返回指针给 Rust,Rust 该如何自动释放这块内存?(提示:实现 Drop trait 并调用 C 的 free)。
本文代码示例:
关注公众号:极客老墨
更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。
