Comprehensive Rust 课程之 Rust in Chromium:第三方 crate 引入与 C++/Rust 互操作实战

原创2026-09-09 17:06:381,372 阅读
文章标签:文档教程

Comprehensive Rust 课程之 Rust in Chromium:第三方 crate 引入与 C++/Rust 互操作实战

导读

本文围绕 Comprehensive Rust 课程中 Chromium 模块(src/chromium.md)展开,讲解 Rust 在 Chromium 中的真实集成方式:Rust 被官方支持用于第三方库,同时以**第一方胶水代码(glue code)**连接 Rust 与既有 C++ 代码。读完本文,你将掌握 Chromium 中 Rust 的两种集成模式、gn/ninja 与 cargo 生态的取舍、添加第三方 crate(Rust 库)的完整流程,以及用 CXX 生成 C++/Rust 边界绑定的实操方法,并了解当日课程以"用 Rust 处理 UTF-8 字符串"为切入点的练习路径。

课程提示语原话:今天,我们将调用 Rust 去做一些与字符串相关的简单处理。如果你恰好负责向用户展示 UTF-8 字符串的代码区域,欢迎在你的代码角落中遵循同样的做法,而不必局限于课程演示的精确位置。


一、Rust 在 Chromium 中的定位:第三方库 + 第一方胶水代码

Chromium 对 Rust 的支持策略非常明确:Rust 用于第三方库(third-party libraries),并由第一方胶水代码连接 Rust 与既有 C++ 代码。这一结论来自 src/chromium.md 的开篇陈述,是整个模块的技术基调。

这种定位有两个关键含义:

  1. 第三方 Rust crate 可以进入 Chromium:Chromium 采用"精选依赖集"(curated set of dependencies)策略,第三方 Rust 库需要经过引入、构建适配和安全审计,才能被纳入 //third_party/rust。
  2. 第一方代码仍然以 C++ 为主:Rust 与 C++ 之间的语言边界(language boundary)由少量第一方胶水代码承担,这些胶水代码负责类型转换与调用衔接。

课程模块随后通过 setup、policy、cargo、adding-third-party-crates、interoperability-with-cpp、testing 六个子章节,将这一策略拆解为可操作的工程流程。


二、环境准备:先能构建并运行 Chromium

在进入 Rust 集成之前,src/chromium/setup.md 要求先确保本地可以构建并运行 Chromium。任何平台、任何构建参数组合都可以,但代码必须相对较新(commit position 1223636 之后,对应 2023 年 11 月):

gn gen out/Debug
autoninja -C out/Debug chrome
out/Debug/chrome # 或 Mac 上:out/Debug/Chromium.app/Contents/MacOS/Chromium

其中:

  • gn gen out/Debug 生成 out/Debug 目录下的构建配置;
  • autoninja -C out/Debug chrome 以 ninja 增量编译方式构建 chrome 目标;
  • 启动产物即为 out/Debug/chrome(Mac 上为 .app 内的可执行文件)。

官方推荐使用 component、debug 构建以换取最快的迭代速度——这本身就是默认配置。课程同时强调,如果尚未准备好 Chromium 构建环境,需要参考官方《How to build Chromium》指南,并提醒:搭建 Chromium 构建环境本身相当耗时。此外建议安装 Visual Studio Code 以便于浏览代码。

关于练习的编排方式:本模块包含一系列相互递进(build on each other)的练习,它们分散在整个课程的不同时段进行,而不是集中在最后统一完成。如果某个时段未完成,下一时段可继续跟上。


三、Chromium Rust 策略:两种集成模式

Chromium 的 Rust 策略文档位于其仓库的 docs/rust.md,核心结论是:Rust 既可用于第一方代码,也可用于第三方代码(见 src/chromium/policy.md)。

3.1 模式一:纯第一方 Rust 代码

第一种形态是直接在 Chromium 内编写第一方 Rust 代码,与既有 C++ 代码在语言边界处互通:

"C++"                           Rust
.- - - - - - - - - -.           .- - - - - - - - - - -.
:                   :           :                     :
: Existing Chromium :           :  Chromium Rust      :
: "C++"             :           :  code               :
: +---------------+ :           : +----------------+  :
: |               | :           : |                |  :
: |         o-----+-+-----------+-+->              |  :
: |               | : Language  : |                |  :
: +---------------+ : boundary  : +----------------+  :
:                   :           :                     :
`- - - - - - - - - -'           `- - - - - - - - - - -'

C++ 侧与 Rust 侧通过语言边界(language boundary)进行调用,边界处由胶水代码完成类型与调用的转换。

3.2 模式二:第三方 crate + 第一方 wrapper(更常见)

第二种形态是引入第三方 crate。由于极少数 Rust 库会直接暴露 C/C++ API,因此通常还需要少量第一方胶水代码(wrapper)来桥接:

"C++"                           Rust
.- - - - - - - - - -.           .- - - - - - - - - - - - - - - - - - - - - - -.
:                   :           :                                             :
: Existing Chromium :           :  Chromium Rust              Existing Rust   :
: "C++"             :           :  "wrapper"                  crate           :
: +---------------+ :           : +----------------+          +-------------+ :
: |               | :           : |                |          |             | :
: |         o-----+-+-----------+-+->            o-+----------+-->          | :
: |               | : Language  : |                | Crate    |             | :
: +---------------+ : boundary  : +----------------+ API      +-------------+ :
:                   :           :                                             :
`- - - - - - - - - -'           `- - - - - - - - - - - - - - - - - - - - - - -'

图中可以看到:既有 C++ 代码 → 语言边界 → Chromium 第一方 Rust "wrapper" → crate API → 既有第三方 Rust crate。

3.3 今日课程聚焦

由于第三方 crate 场景更复杂,当日课程将重点放在:

  • 引入第三方 Rust 库(crates);
  • 编写胶水代码,使这些 crate 能从 Chromium C++ 侧被调用(同样的技术也适用于第一方 Rust 代码)。

四、两种生态的对比:gn/ninja 与 cargo 的取舍

Chromium 的构建体系与 Rust 社区的默认工具链存在本质差异。src/chromium/cargo.md 对此做了系统对比:

特性 C++ 库 Rust crate
构建系统 种类繁多 统一:Cargo.toml
典型库体积 偏大 小
传递依赖(transitive dependencies) 少 多

对于 Chromium 工程师而言,这意味着 Rust crate 之间极容易互相依赖(在 Cargo.toml 中加一行即可),因此在引入时往往需要连带引入多个库,这是优势也是成本。

具体到"如何用 Rust 写 Chromium 代码",有三条可选路径:

  1. 使用 gn + ninja,借助 //build/rust/*.gni 中的模板(例如后续会遇到的 rust_static_library)。好处是使用 Chromium 审计过的工具链与 crate;
  2. 使用 cargo,但自我限制,只使用 Chromium 审计过的工具链与 crate;
  3. 使用 cargo,信任外部工具链和/或从 crates.io 下载的 crate。

课程从此往后的内容聚焦于 gn 与 ninja,因为这是把 Rust 代码构建进 Chromium 浏览器的官方方式;同时强调 Cargo 是 Rust 生态的重要组成部分,应当保留在工具箱中。

4.1 小组练习:评估 cargo 的风险剖面

课程在此设置了一个小型分组讨论练习,让学员评估:

  • 头脑风暴 cargo 可能带来优势的场景,并评估这些场景的风险剖面;
  • 讨论在使用 gn/ninja、离线 cargo 等不同方案时,需要信任哪些工具、库和人群。

讲师备注中给出了一些讨论线索,例如:clap(命令行解析)、serde(序列化/反序列化)、itertools(迭代器工具)等 crate 体验良好,使得编写工具或原型开发非常便捷;rustup 可轻松切换 rustc 版本;Mozilla 的 cargo vet 可简化并共享安全审计(cargo install --locked cargo-vet)。隐含被信任的清单则包括:rustc(及其依赖的 LLVM/Clang、二进制引导编译器)、rustup、cargo、rustfmt、构建与分发预编译工具链的内部基础设施、cargo audit/cargo vet、被 vendored 进 //third_party/rust 的库(由 security@chromium.org 审计)等。


五、添加第三方 crate:从 Cargo.toml 到 gnrt_config.toml

5.1 集中式 Cargo.toml 管理

Chromium 为直接依赖维护了一套集中管理的 crate 依赖清单,位于 third_party/rust/chromium_crates_io/Cargo.toml。其形态与普通 Rust 项目无异(见 src/chromium/adding-third-party-crates/configuring-cargo-toml.md):

[dependencies]
bitflags = "1"
cfg-if = "1"
cxx = "1"
# lots more...

与任何 Cargo.toml 一样,可以指定依赖的更多细节,通常需要指明希望启用的 features。但与其他项目不同的是,把 crate 加进 Chromium 时,往往还需要在另一个文件中补充 Chromium 特定的信息——即下文提到的 gnrt_config.toml。

5.2 gnrt_config.toml:Chromium 特有的 crate 扩展配置

与 Cargo.toml 并排存在的是 third_party/rust/chromium_crates_io/gnrt_config.toml(见 src/chromium/adding-third-party-crates/configuring-gnrt-config-toml.md),它承载 Chromium 对 crate 处理的扩展配置。

每新增一个 crate,至少要指定 group(分组),可选值有三档:

#   'safe': The library satisfies the rule-of-2 and can be used in any process.
#   'sandbox': The library does not satisfy the rule-of-2 and must be used in
#              a sandboxed process such as the renderer or a utility process.
#   'test': The library is only used in tests.

即:

  • safe:该库满足 Chromium 的 rule-of-2(内存安全规则),可在任何进程中使用;
  • sandbox:该库不满足 rule-of-2,必须仅在沙箱化进程(如 renderer 进程或 utility 进程)中使用;
  • test:该库仅用于测试代码。

配置示例:

[crate.my-new-crate]
group = 'test' # only used in test code

此外,视 crate 源码目录布局而定,还可能需要在该文件中指定其 LICENSE 文件的位置。课程后续还会讲到通过该文件解决其他引入问题(对应 adding-third-party-crates/resolving-problems.md 的内容)。

5.3 引入 crate 的完整链路

src/chromium/adding-third-party-crates.md 总结了引入一个 crate 需要完成的三个步骤,课程将依次展开:

  1. 把 crate 放进 Chromium 源码树(对应 downloading-crates.md、checking-in.md:如何获取 crate 源码并纳入 //third_party/rust,涉及 Cargo.toml 与 gnrt_config.toml 的配置、gnrt 工具的使用,以及保持与上游更新的机制 keeping-up-to-date.md);
  2. 为其编写 gn 构建规则(cargo.md 中提到 //build/rust/*.gni 的模板,如 rust_static_library;遇到依赖解析问题时,参考 resolving-problems.md 中需在 gnrt_config.toml 配置的其他项);
  3. 审计其源码以确保足够安全(对应 reviews-and-audits.md:引入前需要代码审查与安全审计)。

课程还特别指出,表格中的对比皆为一般化概括,反例必然存在;但让学生理解"大多数 Rust 代码都依赖其他 Rust 库"这一点很重要,因为这既带来生态红利,也带来依赖治理成本。


六、C++/Rust 互操作:用 CXX 声明边界

6.1 #[cxx::bridge] 与 rust_static_library

Chromium 中,Rust 与 C++ 的互操作基于 CXX 工具链(仓库中携带了其源码副本,见 third_party/cxx)。基本做法(见 src/chromium/interoperability-with-cpp/using-cxx-in-chromium.md):

在 Chromium 中,我们在每个需要使用 Rust 的叶子节点(leaf-node) 定义一个独立的 #[cxx::bridge] mod。通常,每个 rust_static_library 对应一个这样的模块。

具体操作是在已有的 rust_static_library 目标中,在 crate_root 与 sources 之外,再加上:

cxx_bindings = [ "my_rust_file.rs" ]
   # list of files containing #[cxx::bridge], not all source files
allow_unsafe = true

要点:

  • cxx_bindings 列出的是包含 #[cxx::bridge] 的文件,而非全部源文件;
  • allow_unsafe = true 是必需的——任何 C/C++ 代码按 Rust 的安全标准来看都不"安全":与 C/C++ 来回调用可能对内存做任意操作,并危及 Rust 自身数据布局的安全性(讲师备注指出,过多的 unsafe 关键字会影响该关键字的信噪比,且存在争议,但严格来说,任何外部代码进入 Rust 二进制都可能引发 Rust 视角下的意外行为;另一个更具体的理由是 CXX 在后台生成的正是手写 unsafe 与 extern "C" 函数)。

生成 C++ 头文件后,可直接以常规方式包含:

#include "ui/base/my_rust_file.rs.h"

(头文件会生成在合理的位置。)同时,//base 中提供了一些工具函数用于在 Chromium C++ 类型与 CXX Rust 类型之间转换,例如 SpanToRustSlice(位于 base/containers/span_rust.h)。

6.2 cxx::bridge 模块的形态

CXX 要求整个 C++/Rust 边界都在 .rs 源文件内部的 cxx::bridge 模块中声明。课程中给出的示例片段(见 src/chromium/interoperability-with-cpp/example-bindings.md)来自仓库内 third_party/cxx/book/snippets.rs 中的 cxx_overview 代码段。讲师备注强调了几个容易误解的点:

  • 虽然它看起来像普通 Rust mod,但 #[cxx::bridge] 过程宏会对其做复杂处理,生成的代码远比表面看到的更精密;不过最终在你的代码里仍会得到一个名为 ffi 的模块;
  • 该机制原生支持 C++ 的 std::unique_ptr 在 Rust 中的使用;
  • 原生支持 Rust 切片(slices)在 C++ 中的使用;
  • 模块上半部分声明的是"从 C++ 调用 Rust"的方向以及 Rust 类型;
  • 模块下半部分声明的是"从 Rust 调用 C++"的方向以及 C++ 类型;
  • 常见误解:看起来像是 Rust 在解析 C++ 头文件,实则是误导——该头文件从不会被 Rust 解释,它只是被 #include 进生成的 C++ 代码,供 C++ 编译器使用。

其余互操作细节(手写 FFI 与 CXX 的对比、错误处理、类型映射、局限性)可继续阅读 src/chromium/interoperability-with-cpp.md 及其子章节;测试侧则通过 chromium_import! 宏与 gtest interop 验证跨语言行为,见 src/chromium/testing.md。


七、当日练习线索:用 Rust 处理 UTF-8 字符串

回到 src/chromium.md 开篇的任务提示:当日课程的核心实操是调用 Rust 完成与字符串相关的"简单"任务。结合互操作章节可知,其技术路径正是本文所述的完整链路:

  1. 通过集中式 Cargo.toml + gnrt_config.toml 引入(或复用)一个处理字符串的 crate;
  2. 用 rust_static_library 编写/包装第一方 Rust 胶水代码,并通过 #[cxx::bridge] 暴露给 C++;
  3. 在 C++ 侧包含生成的 .rs.h 头文件,完成调用——例如把 UTF-8 字符串传给 Rust 处理后再展示给用户。

这也呼应了课程建议:不必拘泥于演示的精确位置,可以在自己负责的展示 UTF-8 字符串的代码区域实践同样配方。


八、总结

Comprehensive Rust 的 Chromium 模块清晰勾勒了 Rust 进入 Chromium 的完整工程图景:

  • 定位:Rust 用于第三方库,第一方胶水代码负责桥接既有 C++;
  • 构建:以 gn/ninja + rust_static_library 为主路径,同时保留 cargo 作为生态工具;
  • 引入:通过集中式 Cargo.toml 声明依赖、以 gnrt_config.toml 标注 safe/sandbox/test 分组并补充许可证信息,再经 gnrt 生成构建规则,最后完成代码审查与安全审计;
  • 互操作:用 #[cxx::bridge] 声明语言边界,CXX 自动生成 Rust 与 C++ 两侧代码,Chromium 提供 cxx_bindings/allow_unsafe 等 GN 配置项与 base 中的类型转换工具。

这套方法论不仅适用于本次"用 Rust 处理字符串"的练习,也适用于任何希望在 Chromium 中引入 Rust 能力的第一方与第三方场景。

登录后查看全文
comprehensive-rust