news 2026/9/14 6:19:48

ScyllaDB Rust × C++ 互操作实战:基于 cxx::bridge 编写 Rust 包并接入构建系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ScyllaDB Rust × C++ 互操作实战:基于 cxx::bridge 编写 Rust 包并接入构建系统

ScyllaDB Rust × C++ 互操作实战:基于 cxx::bridge 编写 Rust 包并接入构建系统

【免费下载链接】scylladbNoSQL data store using the Seastar framework, compatible with Apache Cassandra and Amazon DynamoDB项目地址: https://gitcode.com/GitHub_Trending/sc/scylladb

本文基于 ScyllaDB 官方开发文档《Rust and C++》(docs/dev/rust.md),完整讲解如何在以 Seastar 框架编写的 C++ 代码库中引入 Rust 模块:包括用cxx::bridge宏导出 Rust 函数的写法、新包的 7 步接入流程、Cargo.lock 提交规范,以及构建系统(configure.py / CMake / cxxbridge / cargo)如何把 Rust 静态库与 C++ 侧生成的头文件、源文件拼装起来。读完后你能独立创建一个可被 C++ 调用的 Rust 包,并理解两侧 ABI 的衔接原理。

1. 为什么 Scylla 要混用 Rust

Scylla 的核心是 C++,但 Rust 提供了一些 C++ 中缺失的有用特性(文档原文即以此开篇)。Scylla 的实际做法是把 Rust 与 C++ 通过 FFI 桥接起来:Rust 代码编译为采用 C++ ABI 的静态库,C++ 代码则通过工具生成的头文件以“原生命名”调用 Rust 方法。仓库中已有两个真实案例:测试用的inc包和 wasm 引擎绑定包wasmtime_bindings,它们分别对应rust/inc/rust/wasmtime_bindings/,后者被lang/wasm相关功能使用(见 rust/src/lib.rs 中的extern crate声明与 configure.py 第 1400 行附近的依赖登记)。

2. 互操作实现原理:CXX crate 与 cxxbridge 代码生成

文档“Rust interoperability implementation”一节给出了完整机制,结合源码可以还原成如下链条:

  1. Rust 侧声明:Rust 代码使用#[cxx::bridge(namespace = "...")]宏,配合mod ffiextern "Rust"块,标记需要导出到 C++ 的项。例如 rust/inc/src/lib.rs 中:

    #[cxx::bridge(namespace = "rust")] mod ffi { extern "Rust" { fn inc(x: i32) -> i32; } } fn inc(x: i32) -> i32 { x + 1 }
  2. 编译产物:Rust 文件编译时生成一个采用 C++ ABI 的静态库,Rust 方法以特殊符号名导出。

  3. C++ 侧生成cxxbridge命令行工具从同一份 Rust 源码生成 C++ 头文件(*.hh)与源文件(*.cc)。头文件以“原始命名”暴露所有导出项,可以像普通 C++ 头文件一样#include*.cc中的 C++ 方法实现内部再转调那些特殊符号名对应的 Rust 实现。

  4. 链接方式:所有 Rust 模块统一编译为单一静态库。文档明确指出,这是 Rust 链接到 C++ 目前唯一官方支持的方式;未来可能借助rlib(Rust library)文件支持更多链接方法。

这一机制在构建系统中的落点见 rust/CMakeLists.txt:generate_cxxbridge()函数(第 42-59 行)对给定的.rs输入运行cxxbridge --header --output ${header}cxxbridge --output ${source}两条命令,分别产出头文件与源文件;add_rust_library()函数(第 11-37 行)则调用cargo build --locked --profile=rust-${mode}编译出lib${name}.a,再包装成 CMake 的Rust::${name}imported target。最终wasmtime_bindingsinc两个 CMake 静态库都由生成代码 + 链接Rust::rust_combined组成(第 79-114 行)。

3. 实操指南:创建一个被 C++ 调用的 Rust 包(7 步流程)

文档给出了以新建包new_pkg、导出fn inc(x: i32) -> i32到命名空间xyz为例的完整流程。以下步骤全部保留,并逐一对照仓库中的实际文件补充说明:

  1. 创建包:在rust目录下执行

    cargo new new_pkg --lib
  2. 登记依赖:在 rust/Cargo.toml 的[dependencies]列表中追加

    new_pkg = { path = "new_pkg", version = "0.1.0" }

    参考现有条目:inc = { path = "inc", version = "0.1.0" }wasmtime_bindings = { path = "wasmtime_bindings", version = "0.1.0" }。注意该文件顶部还声明了crate-type = ["staticlib"],这正是第 2 节所说的“编译为单一静态库”的配置来源。

  3. 加入工作区入口:在 rust/src/lib.rs 中追加extern crate new_pkg;。该文件当前内容为extern crate inc;extern crate wasmtime_bindings;,是三个包汇入同一个rust_combined静态库的汇集点。

  4. 编写包代码:在new_pkg/Cargo.toml中配置依赖(尤其是cxxcrate),在new_pkg/src/lib.rs(及其他new_pkg/src/*.rs)中编写 Rust 代码。

  5. 声明 FFI 导出:在new_pkg/src/lib.rs中添加

    #[cxx::bridge(namespace = "xyz")] mod ffi { extern "Rust" { fn inc(x: i32) -> i32; } }

    namespace参数决定 C++ 侧的调用命名空间,即 C++ 里写成xyz::inc(...)。注意extern "Rust"块只是声明,真正的函数体仍需定义(见 rust/inc/src/lib.rs 中fn inc的实现)。

  6. 登记构建依赖:在 configure.py 中,把new_pkg/src/lib.rs添加到需要使用该 Rust 导出的 C++ 目标的依赖列表。configure.py 生成的 ninja 规则会把rust/下的每个.rs文件映射到对应的gen/rust/*.o编译产物(见 configure.py 第 2736-2737 行:obj = dep[:idx].replace('rust/','') + '.o')。

  7. 在 C++ 中使用#include "rust/new_pkg.hh",然后调用xyz::foo()。以测试包为例,test/boost/rust_test.cc 正是这样写的:

    #include "rust/inc.hh" BOOST_AUTO_TEST_CASE(test_inc) { int k = 1; BOOST_REQUIRE(rust::inc(k) == 2); }

    其构建依赖在 configure.py 第 1837 行登记:deps['test/boost/rust_test'] += ['rust/inc/src/lib.rs']——即第 6 步在真实项目中的形态。

3.1 cxx::bridge 可以放在 lib.rs 之外

文档还说明:cxx::bridge段不必非要放在lib.rs,也可以放在例如abc.rs中。此时必须注意两点:

  • lib.rs中添加mod abc;,确保该 bridge 与整个包一起参与编译;
  • 被导出函数的定义必须在abc.rs的可见范围内——可以直接写在同一文件,也可以通过mod/use引入。

最后,configure.py的依赖登记要用这个文件(abc.rs所在包路径)替代lib.rs

4. 构建系统如何串联 Rust:configure.py、CMake 与 cargo

从源码结构看,Scylla 同时存在两套构建路径:configure.py 生成的 Ninja 构建与 CMake 构建,二者的 Rust 处理逻辑一致。

Ninja 路径(configure.py)

  • cxxbridge 规则:cxxbridge --include rust/cxx.h对输入.rs生成头文件或源文件(configure.py 第 2562-2565 行),--include rust/cxx.h参数把 cxx crate 的通用头cxx.h并入生成头。
  • 静态库规则:cargo build --locked --manifest-path=rust/Cargo.toml --target-dir=... --profile=rust-{mode}(configure.py 第 2682 行),产物为librust_combined.a,其规则声明了对rust/Cargo.lock的依赖(第 2898 行),保证依赖锁定生效。
  • 所有需要 FFI 头文件的 C++ 目标会把$builddir/{mode}/gen/rust/cxx.h加入生成头依赖(第 2863 行)。

CMake 路径(rust/CMakeLists.txt)

  • add_rust_library(rust_combined)先按 CMake 的 build type 选出rust-dev/rust-debug/rust-release等 profile,再执行cargo build --locked --target-dir=... --profile=rust-${build_mode},把librust_combined.a拷贝到构建目录并注册为Rust::rust_combined
  • generate_cxxbridge(wasmtime_bindings ...)generate_cxxbridge(inc ...)分别对 rust/wasmtime_bindings/src/lib.rs 和 rust/inc/src/lib.rs 执行代码生成,并各自建一个 CMake 静态库链接到Rust::rust_combined
  • 构建时还支持通过Scylla_RUSTC_WRAPPER注入RUSTC_WRAPPER环境变量以启用 sccache(CMakeLists.txt 第 4-9 行);启用预编译头时,绑定库还会复用scylla-precompiled-header,且注释解释了 PCH 的 sanitizer 编译标志必须与目标一致的原因(第 89-96 行)。

Cargo profile 与构建模式对齐:rust/Cargo.toml 定义了五个 Scylla 专属 profile——rust-dev(opt-level=2、去符号、关 overflow-checks)、rust-debug(opt-level=1、禁增量)、rust-sanitize(opt-level="s")、rust-release(保留 debug 信息)、rust-coverage。注释明确说明:cargo profilerust-xyz要与 ninja 的xyzmode 配套使用,从而保证 Rust 部分的优化/调试级别与 C++ 侧同一构建模式匹配。

5. 版本锁定:cxx crate 与 cxxbridge CLI 必须严格同步

这是一个容易被忽视但决定能否链接成功的关键约束,仓库中有两处强注释相互印证:

  • rust/inc/Cargo.toml:cxx = { version = "=1.0.83", features = ["c++20"] },用=精确钉死版本,注释说明 cxx crate 生成 FFI 的 Rust 侧,cxxbridge CLI 生成 C++ 侧,两者版本不一致会导致生成的 cxxbridge 符号在链接期无法解析;升级 cxx 必须同步升级 install-dependencies.sh 中的 cxxbridge-cmd 并重建工具链镜像。
  • install-dependencies.sh 第 482 行:cargo install cxxbridge-cmd --version 1.83 --root /usr/local之前的注释(第 477-481 行)重申了同样的 lockstep 要求,当前版本为 1.0.83。

因此在给新包添加cxx依赖时,务必沿用=1.0.83这类精确版本写法,而不是1.x之类的宽松区间。

6. 提交变更:Cargo.lock 的提交规范

文档“Submitting changes”一节规定:Scylla 跟踪rust/Cargo.lock文件,它记录最近一次成功构建所使用的精确依赖版本。以下三类依赖变更发生后,必须提交更新后的 Cargo.lock:

  • 添加新的本地包(作为 Scylla 的依赖使用);
  • 更新已有包中某个依赖的版本;
  • 给某个包添加新依赖。

Cargo.lock 可通过cargo update命令再生。这一规范之所以严格,是因为构建时cargo build全程带--locked标志(见第 4 节两条规则),锁文件与Cargo.toml不一致会直接导致构建失败。

7. 要点回顾

  • 互操作核心是cxxcrate:#[cxx::bridge(namespace = "...")]+mod ffi+extern "Rust"标记导出项,Rust 侧编译为 C++ ABI 静态库,C++ 侧由cxxbridgeCLI 从同一份.rs生成*.hh/*.cc,头文件可像普通 C++ 头一样包含。
  • 新包接入共 7 步:cargo new→ 登记rust/Cargo.tomlrust/src/lib.rsextern crate→ 编写包代码 →cxx::bridge声明 →configure.py登记依赖 → C++ 中#include生成头文件并调用。
  • cxx::bridge可放在lib.rs之外的文件中,但必须在lib.rs中声明mod且函数定义对该文件可见。
  • 当前唯一官方支持的链接方式是统一静态库(crate-type = ["staticlib"]librust_combined.a);未来可能支持rlib
  • 升级cxx必须与cxxbridge-cmd(install-dependencies.sh,当前 1.0.83)版本锁定同步,否则链接期符号解析失败。
  • 任何 Cargo 依赖变更都要提交由cargo update生成的新Cargo.lock,构建以--locked模式强制执行。

参考路径:docs/dev/rust.md、rust/Cargo.toml、rust/src/lib.rs、rust/inc/src/lib.rs、rust/CMakeLists.txt、test/boost/rust_test.cc、configure.py、install-dependencies.sh。

【免费下载链接】scylladbNoSQL data store using the Seastar framework, compatible with Apache Cassandra and Amazon DynamoDB项目地址: https://gitcode.com/GitHub_Trending/sc/scylladb

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 6:16:46

数字时代的认知纠缠与D-O-S三值模型解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 6:15:17

MATLAB中变尺度随机共振的实现与参数优化指南

简介:随机共振是微弱信号检测领域的重要研究方向,在一个非线性系统中,合适强度的噪声可以反直觉地增强微弱信号的可检测性。这份MATLAB代码包聚焦变尺度随机共振实现,适合信号处理、非线性动力学方向的科研人员与研究生动手实践。…

作者头像 李华
网站建设 2026/9/14 6:13:35

车载Android串口开发实战:UART/RS232/RS485通信与调试指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华