场景
很多团队不是从零重写,而是把热点模块逐步迁到 Rust。最现实的路径是:
- C++ 主程序继续跑
- Rust 提供一个动态库
- 双方通过 C ABI 通信
核心原则很简单:跨语言只传 C 兼容类型。
先定义稳定接口
#[repr(C)]
pub struct CalcResult {
pub code: i32,
pub value: i64,
}
#[no_mangle]
pub extern "C" fn calc_sum(a: i64, b: i64) -> CalcResult {
CalcResult { code: 0, value: a + b }
}
#[repr(C)]保证结构体布局可预测extern "C"保证调用约定一致#[no_mangle]让符号名可被 C++ 链接
字符串内存谁分配谁释放
跨边界最容易出问题的是字符串:
- Rust 分配、C++ 释放,或者反过来
- 不同分配器混用导致崩溃
推荐做法:统一由一侧分配和释放,并显式提供 free 函数。
错误处理不要 panic 穿透
FFI 层应该“防炸”:
#[no_mangle]
pub extern "C" fn safe_div(a: i64, b: i64, out: *mut i64) -> i32 {
if out.is_null() {
return -2;
}
if b == 0 {
return -1;
}
unsafe { *out = a / b; }
0
}
返回错误码是最朴素、也最稳的方式。
性能要看端到端
FFI 的一次调用成本不高,但频繁小调用会拖垮收益。经验是:
- 把细粒度调用合并成批处理接口
- 在 Rust 内部完成更多计算
- 减少跨边界来回搬运
收尾
Rust/C++ 混编的关键不在“语法能不能过”,而在边界是否稳定:
- ABI 明确
- 所有权清晰
- 错误模型统一
边界稳了,性能优化才有意义。