惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
Google DeepMind News
Google DeepMind News
Apple Machine Learning Research
Apple Machine Learning Research
有赞技术团队
有赞技术团队
Y
Y Combinator Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
爱范儿
爱范儿
L
LangChain Blog
IT之家
IT之家
酷 壳 – CoolShell
酷 壳 – CoolShell
MongoDB | Blog
MongoDB | Blog
Hugging Face - Blog
Hugging Face - Blog
G
Google Developers Blog
T
Tailwind CSS Blog
Engineering at Meta
Engineering at Meta
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
宝玉的分享
宝玉的分享
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net
Recent Announcements
Recent Announcements
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - PKICA

单片机 MCU,嵌入式,MPU,DSP,FPGA,PLC 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 rust线程-std::thread::park和unpark 配合Builder实现轻量级的线程挂起与唤醒 rust自定义线程属性std::thread::Builder rust线程 rust关联函数 rust高并发设计实践进阶 rust高并发设计实践 rust性能优化与安全边界 联合体union在rust和C语言中有什么区别 rust并发与异步编程 如何预防rust不良的代码设计总结 rust类型系统与零成本抽象 rust内存模型 告别大显存依赖!用 Rust 新一代深度学习框架 Burn 打造纯 CPU 文本分类推理引擎 rust类型系统标记 编译配置解答 git实用命令 rust底层设计理念值得注意的几个地方总结
Rust FFI 安全抽象范式
PKICA · 2026-06-02 · via 博客园 - PKICA

在 C 与 Rust 的混合开发(FFI)中,FFI 黄金法则是确保跨语言调用时内存安全二进制对齐类型收敛以及

零运行时开销的核心准则。它是一套将 Rust 严格的编译期安全与 C 语言自由的底层指针进行完美接轨的工程

规范。

以下是针对开发及通用混合编程的 FFI 黄金法则 深度总结:

法则一:ABI 决定论(Application Binary Interface)

跨语言交互的本质不是代码转换,而是内存和寄存器级别的传参约定

  • 函数必须修饰:所有供 C 侧调用的 Rust 函数必须声明为 pub extern "C",以强迫 Rust 编译器采用标准的 C 语言调用约定(Calling Convention)。
  • 符号必须保活:必须附加 #[no_mangle] 属性,阻止 Rust 编译器混淆函数名,确保 cbindgen 能够识别并生成同名的 C 语言函数声明。
  • 布局必须强制:所有跨 FFI 传递的结构体,必须附加 #[repr(C)] 属性。这保证了 Rust 结构体中字段的内存顺序、对齐方式(Alignment)与 C 语言的 struct 严格保持 100% 一致。

法则二:所有权隔离法则(Ownership Quarantine)

内存泄漏与野指针(Use-After-Free)大多源于所有权边界的模糊。

  • 谁申请,谁释放:在 Rust 侧通过 Box::new() 申请的堆内存,如果转换成裸指针 *mut c_void 传给 C 侧,C 侧绝对不能调用 free() 去释放它。必须在生命周期结束时,将指针传回 Rust 侧,通过 Box::from_raw() 重新收回所有权并触发 Rust 的自动析构(Drop)。
  • 严禁跨边界解引用返回:在导出函数中,如果框架要求返回指针,严禁写成 return &mut *cntx必须通过指针级联强转(如 cntx as *mut _ as *mut c_void)或使用 Box::into_raw() 传递纯粹的内存地址

法则三:内聚不安全,外呈纯安全(Contain Unsafety)

unsafe 的污染范围控制在最小的单元内。

  • 签名保持 Safe:向 Rust 内部框架挂载的回调函数,定义上不要写 pub unsafe extern "C" fn。这会导致外部强类型检查失败。
  • 体内实施 Unsafe:正确的姿势是去掉签名的 unsafe,将其声明为普通的 pub extern "C" fn。仅在函数体内部需要处理 C 指针、执行 transmute 或解引用时,才开启 unsafe { ... } 代码块。
  • 入参非空引用:若 C 侧框架能确保传入的指针绝对不为 NULL,在 Rust 侧的回调定义中可以直接用 &mut SpaceShipState 代替 *mut _。Rust 框架会在入口处自动完成转换,使你在函数内部无需编写任何指针判空代码,直接享受 Safe Rust 的开发体验。

法则四:数据切片的“零拷贝”边界(Zero-Copy Slicing)

数据处理性能的生死线在于避免内存拷贝。

  • 指针 + 长度 $\rightarrow$ 视图:当 C 侧将数据流通过 const uint8_t *inputuint32_t input_len 传给 Rust 时,Rust 侧严禁将其分配(Allocate)到新的 Vec<u8> 中。
  • 利用 Slice 宏:必须立即使用类似 slice::from_raw_parts将其打包为 Rust 的切片 &[u8]。这只是创建了一个内存视窗(Window),开销为零,却能直接调用 Rust 强大的模式匹配和解析工具(如 nom 框架)。

参考实现:

#[macro_export]
macro_rules! build_slice {
    ($buf:ident, $len:expr) => {
        if $buf.is_null() && $len == 0 {
            &[]
        } else {
            unsafe { std::slice::from_raw_parts($buf, $len) }
        }
    };
}

法则五:工具链逆向解耦(Toolchain Isolation)

利用自动化工具(cbindgen)时,要学会“防御性编程”,防止类型污染。

  • 别名抹平冲突:当 Rust 侧要求 *const QuantumContext 而 C 侧老代码写死要求 QuantumContext * 时,不要强行修改任何一侧。在 Rust 侧声明一个别名 type QuantumContextPtr = *const core::QuantumContext;
  • 配置文件欺骗:在 cbindgen.toml 中配置 [export.rename] "QuantumContextPtr" = "QuantumContext *"。这样既满足了 Rust 侧的强类型回调注册,又强行让 cbindgen 在 C 头文件中吐出不带 const 的干净指针,实现了对既有老 C 代码的零侵入、零破坏。

注意:

如果 C 侧调用该函数时传入的是普通指针(*),而导出的头文件里声明的是常量指针(*const),这种情况下确实完全不需要使用类型别名去抹平冲突。

总结

FFI 黄金法则的核心思想可以浓缩为一句话:“在 C 的世界里展现 C 的规矩(提供统一的 void* 和 int 返回码),在 Rust 的世界里恪守 Rust 的方圆(内部强转为安全引用与 Safe 闭包)”。

参考资料:

rust工程化实践卷III juler