谷歌发布 Rust 编码风格指南:涵盖 API 设计与代码组织规范
近日,谷歌发布了针对其内部使用的 Rust 编程语言风格指南。该文档不仅涉及代码格式化,更重点阐述了 API 设计与实现策略的指导原则。

目前,Rust 语言已广泛应用于 Google 的多个核心项目,包括 google3、Android、ChromeOS、Fuchsia 以及 Chrome。这份指南旨在确保跨代码库的 Rust 使用一致性,并确立统一的质量标准。
背景与目标
适用范围
在 Android、Chromium 及其他相关代码库中,该风格指南已与各方协商制定,这些团队计划开始执行此规范。各代码库可能根据自身情况对规则的严格程度设定额外要求,随着采纳进程的推进,相关信息将进一步更新。
指南核心目标
制定此文档的初衷与 C++ 风格指南类似,主要基于以下考量:
- 优先可读性与可维护性:鉴于代码生命周期长,编写阶段投入少量额外精力以提升长期可读性是值得的。
- 支持大规模变更(LSCs)自动化:谷歌内部频繁进行大规模代码重构,风格一致性有助于工具化操作的高效执行。
- 兼容更广泛的 Rust 生态系统:尽可能遵循社区已有的良好风格惯例,以降低学习成本并减少意外。
- 规则需具备实际价值:所有开发者及审查者均需遵守这些规则,因此仅保留具有显著益处的条款。
- 避免琐碎的风格争议:通过明确规则,减少在代码审查(CLs)中因主观审美差异引发的反复讨论。
遵循公开 Rust 风格规则
在不与本指南冲突的前提下,应遵循官方公开的 Rust 风格规则:
- Rust Style Guide:大多数代码格式约定由
rustfmt自动强制执行。 - Rust API Guidelines:API 设计参考标准。
所谓“冲突”,是指本指南建议的模式被 Rust 官方风格指南所反对或视为次要模式的情况。
为何偏离常规 Rust 风格?除了通用的一致性、可读性和 API 设计目标外,谷歌特别强调为大规模变更(LSCs)铺平道路。大型代码库常需进行跨越全库的重构,依赖工具辅助。为此,代码结构需便于工具处理,这不仅需要一致性,更需要刻意采用利于重构的设计模式。
上下文中的 API 设计规则应用
在应用涉及 API 设计的规则时,需区分两类代码:
- 库代码(Library code):供其他项目(应用程序或其他库)使用,解决相对通用的问题。对此类代码,风格指南中的 API 设计规则具有约束力。
- 应用代码(Application code):仅供单一应用程序使用,解决特定问题。对此类代码,API 设计规则为强烈建议。由于应用代码旨在解决具体问题,应优先考虑简洁性和直接性,避免过早的泛化和抽象,聚焦于满足即时需求。
代码组织结构
术语定义:
- Crate:编译单元,包含一个名为“根模块”的匿名 Rust 模块。
- Module:Crate 内的代码组织单元,用于控制名称可见性,可嵌套,也可将代码拆分至文件和目录中。
以下章节指出,在 Chromium 项目中,不建议充分利用将所有模块拆分到不同文件的可能性。
偏好小型 Crate [chrome]
在 Chromium 中,倾向于使用小型 crate,理想情况下每个 crate 仅包含一个文件(专注于单一抽象)。若代码可拆分为小 crate,则应如此操作。
一个 crate 对应一个 rust_static_library (chrome) 规则,可能包含一个或多个 .rs 文件。
补充说明:
- 根模块允许从叶子子模块重新导出公共 API,使子模块成为 crate 的实现细节。
- 无论是否使用子模块,均适用此规则。首先评估 API 是否需在同一 crate 中,再考虑子模块是否有助于组织。
- 若决定使用子模块,请遵循预期的文件结构。
仅在 trait impl 一致性规则强制多个类型和特质必须包含在一个库中,或构建大型基础库时,才允许使用较大的 crate。
理由:鉴于 Chromium 代码库规模巨大,添加依赖会产生非局部影响,细粒度的库目标有助于提升并行编译和构建缓存的性能。
在 rust_library 源中使用扁平文件布局 [chrome]
在 Chromium 中,属于同一个 rust_library(即同一个 Rust crate)的 .rs 源文件必须位于同一目录下。
不要使用子目录来组织属于单个 crate 的文件。如果感觉需要将文件放入子目录,这强烈表明 crate 过大,应当拆分。
这种布局有意区别于开源仓库中常用的 Cargo 目录结构。
例外://third_party 中同时使用 Cargo 构建的 Rust 代码应遵循传统的 Cargo 目录结构。
理由:
- 基于对小 crate 的偏好,大多数
rust_library目标仅包含一个源文件。 - 若为每个目标创建目录,多数目录将只含一个文件。
- 多文件
rust_library目标也不应为子模块创建专用目录,以确保方法一致。
根据主要用户定义项命名源文件和叶子模块
当叶子模块有一个主要的用户定义项(例如 Foo)时,模块应以该项命名(例如 mod foo 和 foo.rs)。
不以特定抽象为主的叶子模块可按需命名。
理由:便于快速识别源文件或叶子模块的内容/目的。
不在非叶子模块中实施 API
非叶子模块中唯一的条目应为模块声明(而非定义)和重新导出(re-exports)。
注意:叶子模块的定义允许包含 mod test 和跨语言绑定模块如 #[cxx::bridge] 等子模块。
例外:在非叶子模块中包含少量核心的“连接事物”的实现代码是可以接受的,目标是避免在这些模块中存在大量代码。
理由:便于查找内容,避免结构与内容混杂。
不使用全局导入(Glob Imports)
除非在以下可自行裁量的情况下,否则不要使用全局导入:
- 内联模块中的
use super::* - 枚举的方法体及主要处理该枚举的函数中的
use enum::* - 非叶子模块中的
pub use submodule::* - C++ 互操作性:由于 C++ 中的同一命名空间在不同 Rust crate 中对应不同的子模块,你可以使用全局导入将命名空间合并到私有副本中。例如:
mod absl {
pub use status::absl::*;
pub use strings::absl::*;
}
在此假设示例中,你可以像在其他地方一样,从 Rust 中使用 absl::Status 和 absl::StrCat。





