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 风格的安全函数

📚 延伸阅读


相关推荐
无相求码16 分钟前
C程序的内存到底怎么分的?栈、堆、全局区一张图讲清楚
c语言
天空'之城1 小时前
C 语言工业级通用组件手写 08:精准软件消抖
c语言·嵌入式开发·工业级组件·软件消抖·按键防抖
稚南城才子,乌衣巷风流2 小时前
ST 表(Sparse Table)算法详解:原理、实现与应用
算法
hold?fish:palm2 小时前
9 找到字符串中所有字母异位词
c++·算法·leetcode
Legendary_0082 小时前
LDR6500 设计指南:Type-C 音频快充二合一配件 PD 取电与插拔时序控制方案
c语言·开发语言·音视频
Sw1zzle2 小时前
算法入门(六):贪心算法 - 基础入门(Leetcode 121/455/860/376/738)
算法·leetcode·贪心算法
青山木2 小时前
Hot 100 --- 岛屿数量
java·数据结构·算法·leetcode·深度优先·广度优先
不会就选b3 小时前
算法日常・每日刷题--<归并排序>1
数据结构·算法
危桥带雨3 小时前
排序算法(快排、归并、计数、基数排序)
数据结构·算法·排序算法