Rust 调用 C 函数的 FFI

Rust 调用 C 函数的 FFI

🧭 基础场景:调用 C 提供的两个函数

我们从一个简单的 C 库开始:

c 复制代码
// libmath.h
int add(int a, int b);
int subtract(int a, int b);
c 复制代码
// libmath.c
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

我们希望在 Rust 中调用 addsubtract 函数。


✅ 最佳实践一:用 extern "C" 声明 C 函数

推荐结构:单独放在 ffi/bindings.rs 文件中

rust 复制代码
// src/ffi/bindings.rs
use libc::c_int;

extern "C" {
    pub fn add(a: c_int, b: c_int) -> c_int;
    pub fn subtract(a: c_int, b: c_int) -> c_int;
}

✅ 使用 libc::c_int 而不是 Rust 的 i32,可确保类型在不同平台对齐。


✅ 最佳实践二:将 unsafe 封装成安全函数

永远不要让 unsafe 出现在业务代码中!

rust 复制代码
// src/ffi/wrapper.rs
use super::bindings;

pub fn safe_add(a: i32, b: i32) -> i32 {
    unsafe { bindings::add(a, b) }
}

pub fn safe_subtract(a: i32, b: i32) -> i32 {
    unsafe { bindings::subtract(a, b) }
}

✅ 这样 unsafe 调用被封装在一层薄薄的安全函数中,最大程度保证调用安全。


✅ 最佳实践三:模块结构清晰划分

推荐的模块结构如下:

复制代码
src/
├── ffi/
│   ├── mod.rs         // pub use wrapper
│   ├── bindings.rs    // extern "C" 接口绑定
│   └── wrapper.rs     // 封装 safe API
├── main.rs            // 主程序
build.rs               // 编译 libmath.c 的脚本
libmath.c / libmath.h  // C 源码

ffi/mod.rs

rust 复制代码
pub mod bindings;
pub mod wrapper;

✅ 最佳实践四:使用 cc crate 自动编译 C 代码

手动编译 .a.so 文件容易出错,推荐使用 Rust 官方的 cc crate:

rust 复制代码
// build.rs
fn main() {
    cc::Build::new()
        .file("libmath.c")
        .compile("math");
}

cc 会自动为你调用 gcc 编译 C 文件,并将其链接到 Rust 项目中。


✅ 最佳实践五:主程序只用安全接口

rust 复制代码
// src/main.rs
mod ffi;

fn main() {
    let result = ffi::wrapper::safe_add(10, 20);
    println!("10 + 20 = {}", result);

    let diff = ffi::wrapper::safe_subtract(30, 15);
    println!("30 - 15 = {}", diff);
}

✅ 业务代码中无任何 unsafe、无任何 extern 细节,一目了然。


✅ 最佳实践六:自动生成绑定(适用于第三方库)

如果你要对接的是大型 C 库(如 SQLite、OpenSSL),手写 extern 声明代价高,容易出错。这时推荐使用 bindgen

bash 复制代码
bindgen libmath.h -o src/ffi/bindings.rs

然后直接使用自动生成的 bindings.rs,无需手写。


✅ 最佳实践七:封装错误处理(可选进阶)

如果 C 函数返回错误码(如 -1 表示失败),可以封装为 Rust 的 Result<T, FfiError>

rust 复制代码
#[derive(Debug)]
pub enum FfiError {
    MathError,
}

pub fn safe_subtract(a: i32, b: i32) -> Result<i32, FfiError> {
    let result = unsafe { bindings::subtract(a, b) };
    if result < 0 {
        Err(FfiError::MathError)
    } else {
        Ok(result)
    }
}

✅ 让 Rust 调用者像操作普通函数一样处理错误,符合语言风格。


🚀 整体结构回顾

text 复制代码
project/
├── libmath.c
├── libmath.h
├── build.rs
├── Cargo.toml
└── src/
    ├── ffi/
    │   ├── mod.rs
    │   ├── bindings.rs
    │   └── wrapper.rs
    └── main.rs

🧾 总结:Rust 调用 C 的 FFI 最佳实践

项目 最佳做法
绑定声明 extern "C" + libc::c_int 明确类型
unsafe 封装 只在最小封装层使用 unsafe
模块划分 拆分为 bindings + wrapper
构建方式 cc crate 自动编译 C 源码
错误处理 推荐封装为 Result<T, Error>(进阶)
自动生成绑定 bindgen 处理大型头文件
应用层接口 只使用 Rust 风格的安全函数

📚 延伸阅读


相关推荐
RuoZoe3 小时前
重塑WPF辉煌?基于DirectX 12的现代.NET UI框架Jalium
c语言
AI软著研究员5 小时前
程序员必看:软著不是“面子工程”,是代码的“法律保险”
算法
FunnySaltyFish6 小时前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
颜酱7 小时前
理解二叉树最近公共祖先(LCA):从基础到变种解析
javascript·后端·算法
蚂蚁背大象8 小时前
Rust 所有权系统是为了解决什么问题
后端·rust
布列瑟农的星空9 小时前
前端都能看懂的rust入门教程(五)—— 所有权
rust
地平线开发者1 天前
SparseDrive 模型导出与性能优化实战
算法·自动驾驶
董董灿是个攻城狮1 天前
大模型连载2:初步认识 tokenizer 的过程
算法
地平线开发者1 天前
地平线 VP 接口工程实践(一):hbVPRoiResize 接口功能、使用约束与典型问题总结
算法·自动驾驶
罗西的思考1 天前
AI Agent框架探秘:拆解 OpenHands(10)--- Runtime
人工智能·算法·机器学习