Rust FFI与C交互:跨语言编程实践
引言
大家好,我是一名正在从Rust转向Python的后端开发者。在实际项目中,我们经常需要与其他语言进行交互,特别是C语言。Rust提供了强大的FFI(Foreign Function Interface)支持,可以方便地与C代码进行互操作。今天,我想和大家分享一下我在Rust FFI编程方面的经验。
FFI基础
什么是FFI?
FFI是一种机制,允许不同编程语言之间相互调用。Rust的FFI主要用于:
- 调用C库
- 被其他语言调用
- 与系统API交互
基本概念
// 外部函数声明
extern "C" {
fn printf(format: *const i8, ...) -> i32;
}
fn main() {
unsafe {
printf("Hello from C!\n\0".as_ptr());
}
}
调用C函数
简单示例
use std::os::raw::c_int;
extern "C" {
fn abs(x: c_int) -> c_int;
}
fn main() {
unsafe {
let result = abs(-42);
println!("abs(-42) = {}", result); // 42
}
}
传递字符串
use std::os::raw::c_char;
use std::ffi::CString;
extern "C" {
fn puts(s: *const c_char) -> c_int;
}
fn main() {
let c_str = CString::new("Hello from Rust!").unwrap();
unsafe {
puts(c_str.as_ptr());
}
}
传递数组
use std::os::raw::{c_int, c_float};
extern "C" {
fn process_array(arr: *const c_float, len: c_int);
}
fn main() {
let arr = [1.0f32, 2.0f32, 3.0f32, 4.0f32, 5.0f32];
unsafe {
process_array(arr.as_ptr(), arr.len() as c_int);
}
}
导出Rust函数给C
基本示例
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
#[no_mangle]
pub extern "C" fn greet(name: *const u8) {
let name_str = unsafe {
std::ffi::CStr::from_ptr(name as *const i8).to_str().unwrap()
};
println!("Hello, {}!", name_str);
}
导出结构体
#[repr(C)]
pub struct Point {
x: f64,
y: f64,
}
#[no_mangle]
pub extern "C" fn create_point(x: f64, y: f64) -> Point {
Point { x, y }
}
#[no_mangle]
pub extern "C" fn distance(p1: Point, p2: Point) -> f64 {
((p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sqrt()
}
内存管理
分配和释放内存
use std::ffi::CString;
use std::os::raw::c_char;
#[no_mangle]
pub extern "C" fn create_string() -> *mut c_char {
let s = CString::new("Hello from Rust").unwrap();
s.into_raw()
}
#[no_mangle]
pub extern "C" fn free_string(s: *mut c_char) {
unsafe {
if !s.is_null() {
let _ = CString::from_raw(s);
}
}
}
使用libc分配器
use libc::{malloc, free, c_void};
use std::ptr;
fn main() {
unsafe {
let ptr = malloc(1024) as *mut i32;
if !ptr.is_null() {
*ptr = 42;
println!("Value: {}", *ptr);
free(ptr as *mut c_void);
}
}
}
实际应用场景
场景1:封装C库
use std::ffi::CString;
use std::os::raw::c_char;
extern "C" {
fn sqlite3_open(filename: *const c_char, db: *mut *mut sqlite3) -> c_int;
fn sqlite3_close(db: *mut sqlite3) -> c_int;
}
struct SqliteDb {
db: *mut sqlite3,
}
impl SqliteDb {
fn open(filename: &str) -> Result<Self, String> {
let c_filename = CString::new(filename).map_err(|e| e.to_string())?;
let mut db: *mut sqlite3 = ptr::null_mut();
let result = unsafe { sqlite3_open(c_filename.as_ptr(), &mut db) };
if result != 0 || db.is_null() {
return Err("Failed to open database".to_string());
}
Ok(Self { db })
}
}
impl Drop for SqliteDb {
fn drop(&mut self) {
if !self.db.is_null() {
unsafe { sqlite3_close(self.db) };
}
}
}
场景2:高性能计算库
use std::os::raw::c_float;
#[no_mangle]
pub extern "C" fn matrix_multiply(
a: *const c_float,
b: *const c_float,
result: *mut c_float,
n: usize,
) {
unsafe {
for i in 0..n {
for j in 0..n {
let mut sum = 0.0f32;
for k in 0..n {
sum += *a.add(i * n + k) * *b.add(k * n + j);
}
*result.add(i * n + j) = sum;
}
}
}
}
场景3:回调函数
use std::os::raw::{c_int, c_char};
use std::ffi::CStr;
type Callback = extern "C" fn(c_int, *const c_char);
extern "C" fn process_items(callback: Callback) {
let items = [1, 2, 3, 4, 5];
for (i, &item) in items.iter().enumerate() {
let msg = format!("Item {}", item);
let c_msg = std::ffi::CString::new(msg).unwrap();
callback(i as c_int, c_msg.as_ptr());
}
}
构建静态库和动态库
静态库
[lib]
name = "mylib"
crate-type = ["staticlib"]
cargo build --release
动态库
[lib]
name = "mylib"
crate-type = ["cdylib"]
cargo build --release
实战项目:Rust日志库供C使用
use std::ffi::CStr;
use std::os::raw::c_char;
#[repr(C)]
pub enum LogLevel {
Debug,
Info,
Warning,
Error,
}
#[no_mangle]
pub extern "C" fn rust_log(level: LogLevel, message: *const c_char) {
let message_str = unsafe {
CStr::from_ptr(message).to_str().unwrap_or("Invalid string")
};
match level {
LogLevel::Debug => println!("[DEBUG] {}", message_str),
LogLevel::Info => println!("[INFO] {}", message_str),
LogLevel::Warning => println!("[WARN] {}", message_str),
LogLevel::Error => eprintln!("[ERROR] {}", message_str),
}
}
#[no_mangle]
pub extern "C" fn rust_log_fmt(level: LogLevel, format: *const c_char, ...) {
use std::ffi::CString;
use std::fmt::Write;
let format_str = unsafe {
CStr::from_ptr(format).to_str().unwrap_or("Invalid format")
};
let mut message = String::new();
let _ = write!(message, "{}", format_str);
rust_log(level, CString::new(message).unwrap().as_ptr());
}
与Python C扩展的对比
| 特性 | Rust FFI | Python C扩展 |
|---|---|---|
| 内存安全 | 编译时保证 | 手动管理 |
| 类型安全 | 编译时检查 | 运行时检查 |
| 性能 | 零开销 | 有一定开销 |
| 互操作性 | 与C无缝 | 与C无缝 |
| 学习曲线 | 较陡峭 | 相对简单 |
总结
Rust的FFI系统提供了强大的跨语言交互能力:
- 调用C库:可以轻松调用现有的C库
- 导出函数:可以将Rust函数导出给C使用
- 内存管理:需要谨慎处理内存分配和释放
- 性能:零运行时开销,性能优异
通过合理使用FFI,我们可以:
- 复用现有的C代码
- 为其他语言提供高性能的Rust库
- 与系统API进行交互
作为从Rust转向Python的开发者,我发现Rust的FFI系统比Python的C扩展更加安全和高效。虽然需要注意内存管理,但类型安全带来的好处是巨大的。
延伸阅读:
1417

被折叠的 条评论
为什么被折叠?



