11.3 调用外部 C 函数
听
Rust 通过 FFI(Foreign Function Interface)支持与 C 语言的无缝互操作。这使得 Rust 程序可以调用现有的 C 库(如 OpenSSL、SQLite、系统 API 等),复用成熟生态,或逐步将 C 项目迁移到 Rust。调用 C 函数的核心机制是使用 extern "C" 声明函数签名,并通过 unsafe 块执行调用。
声明外部函数
要调用 C 函数,需在 Rust 中使用 extern "C" 块声明其签名:
extern "C" {
fn abs(x: i32) -> i32;
fn strlen(s: *const u8) -> usize;
}
这里:
extern "C"指定使用 C 的 ABI(Application Binary Interface);- 参数和返回类型必须与 C 函数完全匹配;
- 所有外部函数默认被视为
unsafe,因为 Rust 无法验证其实现是否安全。
调用 C 函数
调用必须在 unsafe 块中进行:
fn main() {
unsafe {
let x = abs(-42);
println!("abs(-42) = {}", x); // 输出: 42
}
}
注意:即使函数逻辑看似“安全”(如 abs),调用本身仍标记为 unsafe,因为编译器无法保证:
- 函数是否真的存在(链接时才验证);
- 参数是否满足前置条件(如指针非空);
- 函数是否遵守 Rust 的内存模型。
处理字符串与指针
C 使用以 null 结尾的字节数组表示字符串(char*),而 Rust 使用 UTF-8 编码的 &str 或 String。调用前需转换:
use std::ffi::CString;
use std::os::raw::c_char;
extern "C" {
fn puts(s: *const c_char) -> i32;
}
fn main() {
let msg = CString::new("Hello from Rust!").unwrap();
unsafe {
puts(msg.as_ptr());
}
}
CString 确保内容不含内部 null 字节,并以 null 结尾,符合 C 字符串要求。
链接 C 库
若调用的函数来自外部静态库或动态库,需在构建脚本(build.rs)或通过 #[link] 属性指定链接:
#[link(name = "m")] // 链接 libm(数学库)
extern "C" {
fn sin(x: f64) -> f64;
}
对于复杂项目,通常使用 bindgen 工具从 C 头文件自动生成 Rust FFI 声明。
安全边界与封装
直接暴露 unsafe FFI 调用会污染上层代码。最佳实践是将其封装在安全的 Rust 接口中:
fn safe_abs(x: i32) -> i32 {
unsafe { abs(x) }
}
// 或更复杂的封装
use std::ffi::{CStr, CString};
fn get_c_string_length(s: &str) -> Result<usize, std::ffi::NulError> {
let c_str = CString::new(s)?;
Ok(unsafe { strlen(c_str.as_ptr()) })
}
这样,调用者无需接触 unsafe,且封装函数可添加参数校验、错误处理等逻辑。
常见陷阱
- ABI 不匹配:确保使用
"C"而非其他调用约定(如"stdcall"在 Windows 上); - 生命周期错误:传给 C 的指针在 C 使用期间必须有效;
- panic 跨越 FFI 边界:Rust panic 不能穿过 C 代码,否则导致未定义行为;应使用
catch_unwind或避免在 FFI 回调中 panic; - 类型对齐与大小:使用
std::os::raw中的类型(如c_int、c_char)而非直接使用i32、u8,以保证跨平台兼容性。
小结
通过 extern "C" 声明和 unsafe 调用,Rust 能高效、可靠地集成 C 代码。关键在于理解 ABI 兼容性、正确处理数据表示(尤其是字符串和指针),并通过安全封装隔离不安全边界。这一能力使 Rust 成为系统级编程中理想的 C 替代或补充语言。
#Rust 入门教程
分享于 1 周前
上一篇:11.2 原始指针
下一篇:11.4 从 C 调用 Rust 函数