11.4 从 C 调用 Rust 函数
除了调用 C 函数,Rust 还支持将自身函数导出为 C 兼容的接口,供外部 C 程序调用。这种能力使得 Rust 可作为高性能、内存安全的模块嵌入到现有 C/C++ 项目中,例如实现关键算法、解析器或加密逻辑。
要让 Rust 函数能被 C 调用,需满足两个条件:使用 C 的 ABI 和 防止符号名混淆(name mangling)。
使用 extern "C" 导出函数
通过在函数前添加 extern "C",Rust 会按照 C 的调用约定生成函数:
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
extern "C":指定函数使用 C ABI,确保参数传递和返回方式与 C 一致;#[no_mangle]:禁用 Rust 默认的符号名混淆(mangling),使函数名在编译后保持为add,便于 C 代码链接。
静态库或动态库构建
Rust 代码需编译为库(而非可执行文件),供 C 链接。在 Cargo.toml 中配置:
[lib]
crate-type = ["staticlib", "cdylib"]
staticlib→ 生成.a(Unix)或.lib(Windows)静态库;cdylib→ 生成.so(Linux)、.dylib(macOS)或.dll(Windows)动态库。
运行 cargo build 后,可在 target/debug/ 或 target/release/ 目录找到生成的库文件。
C 端调用示例
在 C 代码中声明并调用该函数:
// main.c
#include <stdio.h>
// 声明 Rust 函数
int add(int a, int b);
int main() {
int result = add(3, 4);
printf("3 + 4 = %d\n", result); // 输出: 3 + 4 = 7
return 0;
}
编译并链接(以 Linux 为例):
# 编译 Rust 库
cargo build --release
# 编译 C 程序并链接 Rust 静态库
gcc main.c -L./target/release -l<crate_name> -lpthread -ldl -lm -o main
./main
注意:Rust 标准库依赖一些系统库(如 pthread、dl、m),链接时需一并包含。
处理复杂类型
C 无法直接理解 Rust 的高级类型(如 String、Vec),因此导出函数应使用 C 兼容类型:
- 整数:
i32、u64等(建议使用std::os::raw中的c_int、c_char等); - 指针:
*const u8、*mut c_void; - 结构体:需用
#[repr(C)]控制布局(见 11.5 节)。
例如,导出一个处理字符串的函数:
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
#[no_mangle]
pub extern "C" fn greet(name: *const c_char) -> *mut c_char {
if name.is_null() {
return std::ptr::null_mut();
}
let c_str = unsafe { CStr::from_ptr(name) };
let name_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let output = format!("Hello, {}!", name_str);
match CString::new(output) {
Ok(c_string) => c_string.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
此函数接收 C 字符串,返回新分配的 C 字符串。注意:调用者需负责释放返回的内存(通常通过另一个导出的 free 函数):
#[no_mangle]
pub extern " C" fn free_greeting(s: *mut c_char) {
if !s.is_null() {
unsafe {
let _ = CString::from_raw(s);
}
}
}
错误处理与 panic 安全
Rust 的 panic 绝不能跨越 FFI 边界。若 Rust 函数在被 C 调用时 panic,程序将触发未定义行为(通常 abort)。因此:
- 在导出函数中避免可能 panic 的操作;
- 使用
Result并转换为错误码或输出参数; - 或用
catch_unwind捕获 panic(但仅适用于 unwindable panic,不适用于panic=abort)。
小结
将 Rust 函数暴露给 C,是构建混合系统的重要手段。关键在于使用 extern "C" 和 #[no_mangle] 确保 ABI 兼容与符号可见性,并通过 C 兼容类型传递数据。同时,必须谨慎管理内存生命周期和错误处理,防止 panic 泄露。通过这种方式,Rust 可作为“安全内核”无缝集成到传统 C 生态中,提升整体系统的可靠性。