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 中调用 add 和 subtract 函数。


✅ 最佳实践一:用 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 风格的安全函数

📚 延伸阅读


相关推荐
专注仿真40 分钟前
梯度提升类(XFBoost)算法详解与代码实例
开发语言·人工智能·python·算法·大模型训练·控制算法
小豆庄1 小时前
视频遮罩追踪退场后ID撞号的排查—多目标场景下的失效
opencv·算法·bug·视频·python3.11
Tisfy1 小时前
LeetCode 3882.网格图中最小异或路径:动态规划
算法·leetcode·动态规划
D e re k ZH2 小时前
P5025 [SNOI2017] 炸弹
c++·算法
A boy CDEF girl2 小时前
算法题专栏:优选算法
数据结构·算法·leetcode
怪我冷i2 小时前
Rust 的impl语法详解
开发语言·后端·rust
没落之王2 小时前
三易五行看宋朝的三界确立
大数据·人工智能·算法
wuminyu3 小时前
Java字节码学习总结:字节码如何加载、解析和执行
java·linux·c语言·jvm·c++
坤坤子吖3 小时前
算法学习——单调队列
开发语言·c++·笔记·学习·算法
Js.Tx4 小时前
【具体数学 Concrete Mathematics】2.1.5 一般性的方法
算法·具体数学