it编程 > 编程语言 > rust

C++及Rust无缝互操作的实现

61人参与 2026-07-21 rust

c++ 与 rust 之间实现无缝互操作的主流方法与工程实践,涵盖经典 ffi 桥接、cxx 安全绑定、代码生成工具链以及复杂场景下的异步调用与错误处理。通过真实示例展示如何在现有 c++ 项目中渐进式引入 rust,在保证性能不变的前提下提升内存安全与并发可靠性,为混合系统编程提供可落地的参考规范。

一、为什么需要 c++ 与 rust 无缝互操作

在系统软件、数据库引擎、游戏底层和嵌入式基础设施领域,大量核心逻辑仍由 c++ 承载。rust 凭借所有权模型和零成本抽象,已成为构建安全、并发的高性能组件的最佳选择之一。然而,将整个数十万行的 c++ 代码库一次性重写为 rust 既不现实也不经济,更合理的路线是“增量替换”或“模块级协作”。因此,c++ 与 rust 之间的无缝互操作不再是锦上添花的可选特性,而是混合系统持续演进的新常态。

二、互操作性基础:ffi 与数据契约

两种语言的互操作基石是 c abi。通过 extern "c" 声明,rust 可以导出与 c 兼容的函数签名,c++ 则通过 extern "c" 块来链接这些符号。在这一层,双方必须对内存布局、所有权语义和字符串编码达成严格契约:

下面是一个 rust 导出加法函数供 c++ 调用的最简示例:

// lib.rs
#[no_mangle]
pub extern "c" fn rust_add(a: i32, b: i32) -> i32 {
    a + b
}

对应的 c++ 头文件只需声明:

extern "c" int rust_add(int a, int b);

通过 cmake 或 cargo build 生成的动态/静态库链接后,c++ 端即可直接调用 rust_add。这套手工 ffi 模式虽然简单,却容易因类型不匹配或遗漏销毁函数导致未定义行为,因此在工程实践中更推荐使用自动生成绑定的工具。

三、从 c++ 调用 rust:cbindgen 与手动导出

当 rust 模块需要向 c++ 暴露一批接口时,cbindgen 是最便捷的工具。它扫描 rust 源码中的 extern "c" 函数和 #[repr(c)] 结构体,自动生成对应的 c/c++ 头文件。

3.1 使用 cbindgen 自动生成头文件

在 rust 库项目的 cargo.toml 中设置 crate-type 为 cdylibstaticlib,然后运行 cbindgen --output my_rust_lib.h。生成的头文件可以直接被 c++ 项目包含。例如,一个 rust 计算模块:

/// 计算两个向量的点积
#[no_mangle]
pub extern "c" fn dot_product(a_ptr: *const f64, b_ptr: *const f64, len: usize) -> f64 {
    let a = unsafe { std::slice::from_raw_parts(a_ptr, len) };
    let b = unsafe { std::slice::from_raw_parts(b_ptr, len) };
    a.iter().zip(b.iter()).map(|(&x, &y)| x * y).sum()
}

cbindgen 会为 len 参数生成 uintptr_t 类型,指针参数保持 const double*,c++ 开发者不需要理解 rust 内部细节即可使用。

3.2 手动导出更复杂的对象

对于带状态的对象,通常通过 opaque pointer 模式暴露句柄,并配套构造/析构/方法函数。rust 侧定义结构体和方法,然后导出对应的 c 函数:

pub struct database {
    // 内部状态
}

#[no_mangle]
pub extern "c" fn database_open(path: *const c_char) -> *mut database {
    // ...
}

#[no_mangle]
pub extern "c" fn database_close(db: *mut database) {
    if !db.is_null() {
        unsafe { box::from_raw(db) };
    }
}

c++ 端可以用 std::unique_ptr<database, decltype(&database_close)> 来封装生命周期,实现 raii 式的自动管理。

四、从 rust 调用 c++:bindgen 与 cxx 安全桥接

传统上,调用现有 c++ 库需要先用 c 包装一层,再用 bindgen 生成 rust 绑定。但 cxx 库提供了更现代、更安全的方案,允许直接在 rust 和 c++ 之间传递标准库类型(如 stringvecuniqueptr),并在编译期检查线程安全与所有权。

4.1 cxx 的基本用法

在 cargo.toml 中添加 cxx 依赖,并编写一个 bridge 模块声明共享接口:

#[cxx::bridge]
mod ffi {
    extern "rust" {
        fn process_data(input: &str) -> string;
    }

    unsafe extern "c++" {
        include!("my_cpp_lib/db.hpp");
        type database;

        fn open_database(path: &str) -> uniqueptr<database>;
        fn query(&self, sql: &str) -> string;
    }
}

cxx 会在编译时生成对应的 c++ 和 rust 胶水代码。rust 侧只需要实现 process_data 函数,c++ 侧则实现 open_databasequery。这种方式消除了手工 ffi 中大量的 unsafe 代码,类型转换和内存安全由框架保证。

4.2 在现有 c++ 项目中渐进引入 rust

对于一个大型 c++ 服务,可以先识别出 cpu 密集型、容易引起安全漏洞或并发瓶颈的模块,将其重构为独立 rust 库,通过 cxx 桥接回主程序。构建系统可以使用 cmake 的 externalproject_addfetchcontent 集成 rust 的 cargo 构建,产生的 .a.so 直接链接进最终二进制。

以图像解码器为例,原 c++ 代码可能因缓冲区溢出而频繁崩溃;改用 rust 重写并导出接口后,c++ 上层调用代码几乎不变,但模块内部获得了内存安全保障:

// c++ 调用侧
#include "rust_codec.h"

std::vector<uint8_t> decode_safe(std::span<const uint8_t> input) {
    rust::slice<const uint8_t> slice{input.data(), input.size()};
    auto result = rust_decode(slice);
    return {result.data(), result.data() + result.size()};
}

一旦 rust_decode 内部发现越界,rust 会触发 panic 或以 result 形式返回错误,而不是让 c++ 代码进入未定义行为。

五、高级话题:异步调用与错误传播

混合系统不可避免地涉及异步 io。rust 的 async/.await 和 c++20 的协程在运行时模型上并不直接兼容。推荐的集成模式有两种:

错误处理方面,rust 惯用 result<t, e>。跨边界时,通常定义统一的错误码枚举(#[repr(c)]),并提供获取错误描述的函数。更高级的做法是通过 cxxexception 支持,在 rust 侧抛出 c++ 异常,但需要谨慎处理 unwind 边界。

六、性能与安全权衡

需要明确的是,rust 与 c++ 之间的函数调用开销与纯 c++ 函数调用处于同一数量级,因 ffi 本身只是寄存器传参与跳转。真正的开销来自跨边界时的数据拷贝与序列化。因此,设计互操作接口时应遵循零拷贝原则:优先传递 spanslicebytes 引用,避免频繁的深拷贝。同时,rust 侧的生命周期标注可以指导 c++ 调用方在安全窗口内使用数据,从而在不牺牲性能的前提下消除 use-after-free 风险。

安全性提升是混合系统最直观的收益。据统计,引入 rust 模块后,内存相关的 cve 数量平均下降 70% 以上。但这并不意味着可以忽视 c++ 侧的安全实践,所有跨边界指针仍需仔细审查,尤其当 rust 以 unsafe 读取 c++ 传入的裸指针时。

七、工具链与团队协作

落地 c++/rust 互操作需要建设统一的构建与测试基础设施。推荐以下组合:

八、总结与展望

c++ 与 rust 的无缝互操作已经从实验走向工程化。cxx 等工具的成熟让开发者可以用接近内部调用的体验混合使用两种语言,cargo 与 cmake 的集成让构建系统变得平滑。未来,随着 carbon 等实验性语言的探索,以及标准委员会对互操作性 abi 的讨论,我们可以预见一种“多语言、单一运行时”的新常态:一个系统由最合适的语言编写各自模块,却像同一个语言构建的那样协同工作。

对今天的工程师而言,最重要的不是等待完美方案,而是从一个小型模块开始尝试混合编程,例如把一段稠密计算的核心循环用 rust 重写,用数据验证其安全与性能收益,再逐步推广到更广泛的组件中去。

到此这篇关于c++及rust无缝互操作的实现的文章就介绍到这了,更多相关c++及rust无缝互操作内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

您想发表意见!!点此发布评论

推荐阅读

Rust 的 Send 与 Sync 自动推导机制之类型系统如何帮助你写出线程安全的代码

07-22

Rust 错误处理分层之库代码别急着打印日志(最新整理)

08-02

为什么 Rust 没有空指针

05-10

Rust编译报错:link.exe未找到问题分析及解决

04-28

Nginx 通过ssl_trusted_certificate正确补全中间证书

04-20

Rust 堆内存指针 Box的实现示例

04-19

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论