comprehensive-rust 课程解读:CXX Bridge 模块——用 `[cxx::bridge]` 搭建 Rust 与 C++ 的安全互操作桥梁
CXX 是目前 Rust 与 C++ 安全互操作的主流方案,而 #[cxx::bridge] 是它的核心入口。本文以 comprehensive-rust 课程(Google Android 团队使用的 Rust 教学材料)中 src/android/interoperability/cpp/bridge.md 为主线,结合仓库内完整的 blobstore 示例,讲透 Bridge 模块的声明方式、三种区块(共享类型、extern "Rust"、extern "C++")、自动生成代码的机制、跨语言类型映射,以及在 Android 构建系统中的落地方式。读完你将能独立读懂并编写一个最小的 CXX 桥接模块。
一、Bridge 模块是什么:一份跨语言的“签名契约”
CXX 与手写 FFI(直接写 extern "C")的本质区别在于:CXX 不要求你手动保证两边签名一致,而是要求你用一份权威的声明来描述两种语言之间暴露的函数签名,这份声明就是 Bridge 模块。
Bridge 模块是一个被 #[cxx::bridge] 属性宏注解的普通 Rust 模块,模块内部通过 extern 块来声明跨语言接口。课程的原文定义如下:
CXX relies on a description of the function signatures that will be exposed from each language to the other. You provide this description using extern blocks in a Rust module annotated with the
#[cxx::bridge]attribute macro.
这句话拆解出三个关键点:
- 描述对象:从每种语言(Rust 与 C++)暴露给另一种语言的函数签名;
- 描述方式:模块内的
extern块; - 触发机制:
#[cxx::bridge]属性宏。
当编译器看到 #[cxx::bridge] 后,CXX 会基于这份声明同时生成匹配的 Rust 类型/函数定义与 C++ 类型/函数定义,把两种语言的项目符号(symbols)对齐起来。也就是说,Bridge 模块既是“需求说明书”,也是代码生成器的“输入原料”。
二、完整示例:blobstore 的 Bridge 模块
课程的示例取自仓库中的 third_party/cxx/blobstore/src/main.rs,这是一个演示 CXX 用法的完整可运行项目。它的 Bridge 模块如下:
#[allow(unsafe_op_in_unsafe_fn)]
#[cxx::bridge(namespace = "org::blobstore")]
mod ffi {
// Shared structs with fields visible to both languages.
struct BlobMetadata {
size: usize,
tags: Vec<String>,
}
// Rust types and signatures exposed to C++.
extern "Rust" {
type MultiBuf;
fn next_chunk(buf: &mut MultiBuf) -> &[u8];
}
// C++ types and signatures exposed to Rust.
unsafe extern "C++" {
include!("include/blobstore.h");
type BlobstoreClient;
fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
fn put(self: Pin<&mut BlobstoreClient>, parts: &mut MultiBuf) -> u64;
fn tag(self: Pin<&mut BlobstoreClient>, blobid: u64, tag: &str);
fn metadata(&self, blobid: u64) -> BlobMetadata;
}
}
这个例子浓缩了 Bridge 模块的几乎全部语法要素,逐行解读:
#[cxx::bridge(namespace = "org::blobstore")]:为 C++ 侧生成的代码指定命名空间,避免符号冲突。生成的 C++ 类、函数会落入org::blobstore命名空间。mod ffi:桥接逻辑统一放在名为ffi的模块内,这是课程的约定俗成——bridge.md 明确指出 "The bridge is generally declared in anffimodule within your crate"。- 共享结构体
BlobMetadata:字段对两种语言都可见,usize与Vec<String>会被分别映射为两侧对应的类型。 extern "Rust"区块:声明 Rust 侧对外暴露的类型与函数,C++ 可以调用它们;unsafe extern "C++"区块:声明 C++ 侧的类型与函数,Rust 可以调用它们。
桥接的 Rust 侧实现
extern "Rust" 中声明的 MultiBuf 和 next_chunk 并非凭空定义,它们必须真实存在于 ffi 模块的父级作用域中。在 main.rs 里可以看到对应实现:
pub struct MultiBuf {
chunks: Vec<Vec<u8>>,
pos: usize,
}
pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] {
let next = buf.chunks.get(buf.pos);
buf.pos += 1;
next.map_or(&[], Vec::as_slice)
}
MultiBuf 是“非连续文件对象上的连续块迭代器”——课程注释里说明,教学实现用 Vec<Vec<u8>> 简化,真实场景可能遍历 rope 之类的复杂数据结构或从某处惰性加载 chunk。
桥接的 C++ 侧实现
extern "C++" 中通过 include!("include/blobstore.h") 指定对应的 C++ 头文件,实际文件为 third_party/cxx/blobstore/include/blobstore.h。其中的 BlobstoreClient 是货真价实的 C++ 类:
namespace org {
namespace blobstore {
class BlobstoreClient {
public:
BlobstoreClient();
uint64_t put(MultiBuf &buf);
void tag(uint64_t blobid, rust::Str tag);
BlobMetadata metadata(uint64_t blobid) const;
// ...
};
std::unique_ptr<BlobstoreClient> new_blobstore_client();
} // namespace blobstore
} // namespace org
桥接声明中的 fn put(self: Pin<&mut BlobstoreClient>, ...) 对应 C++ 的成员函数 put,fn metadata(&self, ...) 对应 const 成员函数。可见 Bridge 声明与真实头文件签名一一对应,这正是 CXX 能完成静态校验的前提。
Rust 侧如何调用 C++ 实现
在 main 函数中,Rust 代码像调用本地函数一样调用桥接出来的 C++ 能力:
fn main() {
let mut client = ffi::new_blobstore_client();
// Upload a blob.
let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()];
let mut buf = MultiBuf { chunks, pos: 0 };
let blobid = client.pin_mut().put(&mut buf);
println!("blobid = {}", blobid);
// Add a tag.
client.pin_mut().tag(blobid, "rust");
// Read back the tags.
let metadata = client.metadata(blobid);
println!("tags = {:?}", metadata.tags);
}
注意 client.pin_mut():因为 put 以 self: Pin<&mut BlobstoreClient> 声明,Rust 侧需要先取得可变 Pin 引用才能调用。
三、三种区块:Bridge 模块的内部结构
从 blobstore 示例可以看到,一个完整的 Bridge 模块由三部分组成,课程将后两部分拆成独立小节讲解:
1. 共享类型(Shared Types)
直接写在 mod ffi 内部的 struct 与 enum,C++ 与 Rust 两侧都能看见其完整布局。课程在 shared-types.md 中的示例:
#[cxx::bridge]
mod ffi {
#[derive(Clone, Debug, Hash)]
struct PlayingCard {
suit: Suit,
value: u8, // A=1, J=11, Q=12, K=13
}
enum Suit {
Clubs,
Diamonds,
Hearts,
Spades,
}
}
课程的注意事项值得牢记:
- 只支持 C 风格(无字段的单元)枚举,带字段的数据枚举不能作为共享类型;
#[derive()]支持有限,且派生能力会同步移植到 C++ 侧——例如 Rust 侧derive(Hash)后,CXX 也会为对应的 C++ 类型生成std::hash的实现。
2. extern "Rust":把 Rust 暴露给 C++
课程在 rust-bridge.md 中给出精简示例:
#[cxx::bridge]
mod ffi {
extern "Rust" {
type MyType; // Opaque type
fn foo(&self); // Method on `MyType`
fn bar() -> Box<MyType>; // Free function
}
}
struct MyType(i32);
impl MyType {
fn foo(&self) {
println!("{}", self.0);
}
}
fn bar() -> Box<MyType> {
Box::new(MyType(123))
}
两个机制细节:
extern "Rust"中列出的条目引用的是父模块作用域内的真实条目,上面MyType、foo、bar就是在模块外定义的;- CXX 代码生成器会依据你的
extern "Rust"区块生成一个 C++ 头文件,其中包含对应的 C++ 声明。该头文件的路径与包含 bridge 的 Rust 源文件路径相同,只是扩展名换成.rs.h。例如源文件是src/main.rs,生成的头文件就是src/main.rs.h。
3. extern "C++":把 C++ 暴露给 Rust
课程在 cpp-bridge.md 中说明,extern "C++" 区块(blobstore 中写作 unsafe extern "C++")会被展开为大致如下的 Rust 代码:
#[repr(C)]
pub struct BlobstoreClient {
_private: ::cxx::private::Opaque,
}
pub fn new_blobstore_client() -> ::cxx::UniquePtr<BlobstoreClient> {
extern "C" {
#[link_name = "org$blobstore$cxxbridge1$new_blobstore_client"]
fn __new_blobstore_client() -> *mut BlobstoreClient;
}
unsafe { ::cxx::UniquePtr::from_raw(__new_blobstore_client()) }
}
impl BlobstoreClient {
pub fn put(&self, parts: &mut MultiBuf) -> u64 {
extern "C" {
#[link_name = "org$blobstore$cxxbridge1$BlobstoreClient$put"]
fn __put(
_: &BlobstoreClient,
parts: *mut ::cxx::core::ffi::c_void,
) -> u64;
}
unsafe {
__put(self, parts as *mut MultiBuf as *mut ::cxx::core::ffi::c_void)
}
}
}
从这段展开代码可以观察到 CXX 的两个核心设计:
- Rust 侧的不透明类型
BlobstoreClient是一个#[repr(C)]结构体,内部只有_private: ::cxx::private::Opaque,Rust 无法直接访问其字段——所有权与内存布局完全由 C++ 侧控制; - 底层仍然走
extern "C"FFI,但符号名(link_name)由 CXX 统一编排(如org$blobstore$cxxbridge1$...),并借助UniquePtr、Pin等封装隐藏了裸指针操作。
课程强调两个要点:
- 程序员不需要“保证”自己写的签名准确无误:CXX 会执行静态断言,严格校验你声明的签名与 C++ 头文件中实际声明完全一致,不一致直接编译失败;
unsafe extern块允许声明从 Rust 调用是安全的 C++ 函数:把“这个 C++ 函数可以安全调用”这一承诺交给声明者,调用点无需再写unsafe。
四、生成代码:Rust 侧与 C++ 侧分别长什么样
生成的 C++ 代码
课程在 generated-cpp.md 中展示了 extern "Rust" 区块生成出的 C++ 声明(源文件是 blobstore 的 bridge):
struct MultiBuf final : public ::rust::Opaque {
~MultiBuf() = delete;
private:
friend ::rust::layout;
struct layout {
static ::std::size_t size() noexcept;
static ::std::size_t align() noexcept;
};
};
::rust::Slice<::std::uint8_t const> next_chunk(::org::blobstore::MultiBuf &buf) noexcept;
关键信息:
- Rust 的不透明类型
MultiBuf在 C++ 侧是一个继承::rust::Opaque的final结构体,析构函数被delete,因为其生命周期由 Rust 管理; - 函数签名被翻译成 C++ 原生风格:
&mut MultiBuf变成引用,&[u8]变成::rust::Slice<::std::uint8_t const>,且带noexcept; - 注意生成的代码同样被放入
org::blobstore命名空间,印证了namespace属性的作用。
到哪里看生成代码
课程 bridge.md 给出了两种查看生成代码的方法:
- Rust 侧:使用 cargo-expand 展开过程宏。大多数示例直接运行
cargo expand ::ffi只展开ffi模块即可(课程特别注明:这个方法不适用于 Android 项目,因为 Android 项目通常不在本机直接构建 Rust crate); - C++ 侧:查看构建产物目录
target/cxxbridge,里面就是生成的.rs.h/.rs.cc文件。
五、跨语言类型映射:哪些类型可以穿越大桥
并不是所有类型都能在两侧直接传递,CXX 维护着一张白名单。课程在 type-mapping.md 中给出完整映射表:
| Rust 类型 | C++ 类型 |
|---|---|
String |
rust::String |
&str |
rust::Str |
CxxString |
std::string |
&[T] / &mut [T] |
rust::Slice |
Box<T> |
rust::Box<T> |
UniquePtr<T> |
std::unique_ptr<T> |
Vec<T> |
rust::Vec<T> |
CxxVector<T> |
std::vector<T> |
这些类型可以用在共享结构体的字段里,也可以用作 extern 函数的参数与返回值。
课程特别解释了为什么 Rust 的 String 不直接映射到 std::string:
- UTF-8 不变量:
std::string是任意字节序列,不维护String要求的 UTF-8 合法性约束; - 内存布局不同:两种类型在内存中的布局不一致,无法直接在两种语言间原样传递;
- 移动语义不匹配:
std::string依赖移动构造函数,这与 Rust 的 move 语义不同,因此std::string不能按值传给 Rust。
所以 CXX 为每种语言各维护了一套“镜像类型”(rust::String、rust::Str、rust::Vec 等),由 rust/cxx.h 提供(见 blobstore.h 顶部的 #include "rust/cxx.h")。
共享枚举的特殊实现
课程在 shared-enums.md 中揭示了共享枚举在 Rust 侧的“惊人”真相——它生成的其实是一个包装数值的透明结构体,而不是真正的 Rust enum:
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct Suit {
pub repr: u8,
}
#[allow(non_upper_case_globals)]
impl Suit {
pub const Clubs: Self = Suit { repr: 0 };
pub const Diamonds: Self = Suit { repr: 1 };
pub const Hearts: Self = Suit { repr: 2 };
pub const Spades: Self = Suit { repr: 3 };
}
原因在课程中说得非常清楚:在 C++ 中,enum class 持有不在列举范围内的值并不构成未定义行为(UB),为了让 Rust 表示与 C++ 行为保持一致,Rust 侧就不能使用会假设值域闭合的真枚举,而必须退化为可容纳任意 u8 的透明包装类型。
六、在 Android 构建系统中落地:两条 genrule
Bridge 模块最终要融入 Android 构建体系。课程 android-cpp-genrules.md 展示了标准做法——用两条 genrule 分别生成 CXX 头文件与 CXX 源码,再作为 cc_library_static 的输入:
// Generate a C++ header containing the C++ bindings
// to the Rust exported functions in lib.rs.
genrule {
name: "libcxx_test_bridge_header",
tools: ["cxxbridge"],
cmd: "$(location cxxbridge) $(in) --header > $(out)",
srcs: ["lib.rs"],
out: ["lib.rs.h"],
}
// Generate the C++ code that Rust calls into.
genrule {
name: "libcxx_test_bridge_code",
tools: ["cxxbridge"],
cmd: "$(location cxxbridge) $(in) > $(out)",
srcs: ["lib.rs"],
out: ["lib.rs.cc"],
}
要点:
cxxbridge是独立的命令行工具,专门负责生成 bridge 模块的 C++ 侧代码,它已随 Android 内置、可作为 Soong tool 直接使用;- 命名约定:如果 Rust 源文件是
lib.rs,那么生成的头文件叫lib.rs.h、源文件叫lib.rs.cc(不过这只是约定,并非强制); - 第一条 genrule 用
--header标志生成 C++ 头文件,第二条不带标志生成 C++ 实现源文件,两者作为输入喂给cc_library_static,最终与 Rust 侧产物链接成一个完整的混合库。
七、综合理解:CXX 桥接的完整工作流
将课程各章节串起来,一个 CXX 桥接的完整工作流是:
- 在 crate 内的
ffi模块上标注#[cxx::bridge(namespace = "...")](对应 bridge.md); - 在模块内按需声明共享 struct/enum、
extern "Rust"、extern "C++"三种区块,写清跨语言签名(对应 rust-bridge.md 与 cpp-bridge.md); - 在父模块中实现
extern "Rust"声明的类型与函数;在 C++ 头文件中实现extern "C++"声明的类型与函数(参考 blobstore.h); - CXX 通过过程宏在编译期生成两侧绑定代码,并用静态断言校验签名一致性,任何不匹配都会在编译期暴露(对应 generated-cpp.md);
- 用
cargo expand ::ffi检查生成的 Rust 代码、到target/cxxbridge检查生成的 C++ 代码;在 Android 中用两条cxxbridgegenrule 接入cc_library_static(对应 android-cpp-genrules.md)。
借助这套机制,Rust 与 C++ 的互操作从“手写 extern "C" + 裸指针 + 手工对齐布局”升级为“声明式桥接 + 编译期校验”,这也是 CXX 被课程选中作为 Android Rust 互操作教学主线的根本原因。本仓库还提供了与 Bridge 模块配套的错误处理(Result 传播)、异常映射等进阶内容,可在 src/android/interoperability/cpp/ 目录下继续深入学习。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00