Rust FFI
通过 C ABI 与外部语言互操作;安全封装、所有权跨边界与工具链要点。
#concept
#growing
#dev
#rust
[!info] related notes
Rust FFI
这个概念为什么出现
现实系统有 C 库、OS API、其他语言运行时。
FFI(Foreign Function Interface)让 Rust 调用/被调用,但类型与所有权契约需人工保证。
[!abstract] 一句话理解
extern "C"声明 C ABI 函数;跨边界指针与生命周期须有明确契约,不能都靠运行时检查证明;可用 bindgen 生成声明,unsafe 留在薄封装内。
最小可运行示例
场景:在有 C 标准库的目标上调用 abs
use std::ffi::c_int;
unsafe extern "C" {
fn abs(input: c_int) -> c_int;
}
fn checked_abs(value: c_int) -> Option<c_int> {
if value == c_int::MIN {
return None; // C abs 的结果无法表示时是未定义行为,不能调用。
}
// SAFETY: 签名匹配本平台 C abs;已排除结果无法表示的最小值。
Some(unsafe { abs(value) })
}
fn main() {
println!("abs={:?}", checked_abs(-12));
assert_eq!(checked_abs(c_int::MIN), None);
}
使用 Rust 2024 的 unsafe extern 块;在带 C 标准库的 Linux 主机可用 rustc 编译,输出 abs=Some(12)。其他目标需核实 C 运行时和链接配置。实际 Rust 代码无需为绝对值引入 FFI,可用整数 checked_abs;这里专门演示 ABI 与输入契约。
结合场景再看三个关注点
- 跨边界无借用检查器
- 谁 free 内存必须写清
- 字符串用
CString/CStr
核心概念与准确模型
- ABI:
"C"最常见 - 表示:repr(C) 控制结构体布局,不使 String、Vec 或 Rust 引用自动成为可移植 C 接口;字段类型本身也要兼容
- 工具:
bindgen、cbindgen - 回调、线程、异常(C++)更复杂
设计动机
- 复用生态
- 渐进迁移
- 性能关键路径
边界与误区
- 把
String指针直接丢给 C - 忽略对齐/打包
- 不让 panic 或外部异常任意跨越 ABI;Rust panic 经过不允许 unwind 的 C ABI 边界会终止进程,C-unwind 是另一份需明确设计的契约
[!warning] 常见误区:大量 pub unsafe fn 直接暴露 能证明所有安全调用都有效时才给 safe 封装;无法建立该保证时保留 unsafe API 与 Safety 文档。bindgen 不会自动证明内存所有权、长度或回调生命周期。
工程实践
- 自动生成绑定
- 所有权表:谁分配谁释放
- 测试与 sanitizer 验证受支持场景;Miri 对实际外部 C 调用支持有限,不能代替目标平台集成验证
- 文档与示例强制
本节总结
- FFI 是契约工程
- unsafe 薄封装
- 布局与字符串是高发坑
自测题
- 为什么需要
repr(C)? - 谁应该 free C 分配的内存?
参考答案
- 保证与 C 兼容的字段布局。
- 按 API 契约,通常分配方或文档指定方。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| The Book — FFI | 官方书 | extern |
| Rustonomicon — FFI | 官方 | FFI 细节 |
| bindgen | 工具 | 生成绑定 |