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

📚 延伸阅读


相关推荐
今天背单词了吗98014 分钟前
算法学习笔记:8.Bellman-Ford 算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·开发语言·后端·算法·最短路径问题
手握风云-24 分钟前
优选算法的链脉之韵:链表专题
数据结构·算法·链表
Coding小公仔28 分钟前
LeetCode 151. 反转字符串中的单词
开发语言·c++·算法
稳兽龙28 分钟前
P1098 [NOIP 2007 提高组] 字符串的展开
c++·算法·模拟
墨小傲30 分钟前
基于Linux下的vscode c/c++开发环境搭建详细教程
linux·c语言·vscode
G.E.N.33 分钟前
开源!RAG竞技场(2):标准RAG算法
大数据·人工智能·深度学习·神经网络·算法·llm·rag
写个博客38 分钟前
暑假算法日记第三天
算法
✿ ༺ ོIT技术༻1 小时前
剑指offer第2版:动态规划+记忆化搜索
算法·动态规划·记忆化搜索
Humbunklung1 小时前
Rust方法语法:赋予结构体行为的力量
开发语言·后端·rust
萧曵 丶1 小时前
Rust 内存结构:深入解析
开发语言·后端·rust