FFI:Python、Rust、C 为什么能互相调用?

TL;DR

FFI 让不同语言通过共同的调用约定交换函数、数据和内存责任。本文从 Rust 调用 C 的例子讲清结构体布局、字符串、所有权、线程安全与 unsafe 边界,说明跨语言桥梁为什么需要最小合同。

FFI 让不同语言通过共同的调用约定交换函数、数据和内存责任。本文从 Rust 调用 C 的例子讲清结构体布局、字符串、所有权、线程安全与 unsafe 边界,说明跨语言桥梁为什么需要最小合同。

FFI:Python、Rust、C 为什么能互相调用?

当 Rust 调用 C 库、Python 调用 C 扩展、JavaScript 调用 WebAssembly 时,语言之间需要一座桥。FFI,Foreign Function Interface,中文常译为“外部函数接口”,描述的正是这种跨语言边界:一边如何声明函数,另一边如何传递参数、返回结果和管理内存。

跨语言调用真正难在哪里

函数名只是表面。两种语言还必须约定整数宽度、结构体布局、字符串是否以零结尾、数组长度由谁提供、指针是否为空、内存由谁分配和释放,以及异常和线程安全如何处理。

Rustonomicon 用 C 接口示例展示了典型做法:通过 extern 声明外部函数,使用链接属性寻找库,并在需要时用 #[repr(C)] 保证结构体布局与 C 兼容。Rust 编译器无法检查外部库的真实实现,所以外部函数通常需要在 unsafe 块中调用。

为什么 FFI 边界通常不应该太宽

边界越宽,双方要共享的语义越多。把复杂对象、异常、回调和所有权直接穿过语言边界,出错时很难定位。更稳妥的接口会把边界收敛成少数稳定的 C ABI 函数,明确输入长度和释放函数,并在进入安全代码前做验证。

字符串是常见陷阱。Rust 的字符串不天然以 \0 结尾,C 字符串却经常依赖 NUL terminator;一个看似普通的转换,如果忘了终止符或生命周期,可能产生截断、越界或悬空指针。

AI 编程的实际提醒

让 AI 生成绑定代码时,不能只让它“把类型翻译过去”。应该同时要求列出 ABI、所有权、线程安全、错误码、释放方式和测试方案。跨语言接口最危险的 bug,往往不是语法错误,而是两种语言都认为责任在对方。

读者应该记住

FFI 的本质是把两种语言都能理解的最小合同写清楚。函数可以互相调用,不代表内存、异常和生命周期已经自动安全;边界设计才是跨语言工程的核心。

资料:The Rustonomicon:Foreign Function InterfaceRust 中安全与不安全的交界

KEEP READING