谷歌发布Rust风格指南:规范API与代码组织

谷歌发布Rust编码风格指南,规范API设计与代码组织。该指南覆盖Android、Chrome等核心项目,旨在提升大规模重构效率并统一质量标准,优先保障代码长期可读性与可维护性。

谷歌发布 Rust 编码风格指南:涵盖 API 设计与代码组织规范

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

偏好小型Crate与扁平布局

目前,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。

评论 0

0/500

评论需审核后展示,请文明发言

💬
还没有评论,来说两句

相关阅读