编程系统开发者

解释一下 Rust 中的系统 FFI(外部函数接口)是如何工作的。调用 C 中的函数时有哪些要求和陷阱?

用 Hintsage AI 助手通过面试

答案

Rust 中的 FFI 允许调用在外部库(例如 C/C++)中声明的函数,并将 Rust 函数导出。为此使用关键字 extern。要求:

  • 所有 FFI 调用必须包裹在 unsafe 声明中;
  • ABI(应用程序二进制接口)必须匹配(通常为 "C");
  • 数据类型必须兼容 — 重要的是使用具有固定大小的原始数据类型(i32, u64 等);
  • 内存管理 — 对于所有权、内存泄漏和双重释放非常谨慎。

包装示例:

extern "C" { fn abs(input: i32) -> i32; } fn main() { unsafe { println!("{}", abs(-5)); } }

从 Rust 导出一个函数以供 C 使用如下:

#[no_mangle] pub extern "C" fn sum(a: i32, b: i32) -> i32 { a + b }

设陷阱的问题

问题: Rust 是否保证如果将 C 函数调用包装在 unsafe 中,所有的线程安全性和 UB(未定义行为)都能正确工作?

答案: 不! unsafe 是对编译器的承诺,您自己保证所有安全性要求(别名、安全性、内存访问)的正确性。Rust 不会检查 C 内部的代码。例如,库代码中的竞争条件或 UB 可能会“破坏” Rust 程序的运行时。即使 Rust 代码编译通过,实际执行仍可能导致崩溃。


故事

在一家金融数据提供者中,团队通过 FFI 集成了 C 库,但没有检查类型的大小。在 x86_64 上,预期 longi64 是匹配的,但在其他平台上发现大小不匹配 — 错误的内存读取导致了计算中的 bug。

故事

开发者为 C API 创建了一个包装,忘记了传递指向缓冲区的指针。由于 Rust 自动释放内存,C 函数有时会处理已经被释放的内存,导致崩溃。

故事

在客户端-服务器产品中,Rust 模块访问 C 库时没有考虑多线程安全。该库不是线程安全的,而 Rust 程序同时从不同线程访问它,导致崩溃和数据损坏。