Carbon 语言 C++ 风格指南:以 Google 风格为基线的最小化增补方案(提案 p000113 解读)
【免费下载链接】carbon-langCarbon Language's main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang
导读
本文围绕 Carbon 语言仓库中的提案 p000113-add-a-c-style-guide.md 展开,解读 Carbon 项目为何选择 Google C++ 风格指南作为基线、又针对自身需求做了哪些最小化聚焦的增补与调整,并对照仓库中最终落地的 C++ 风格指南、.clang-format 与 .clang-tidy 配置,说明这些规则如何被工具化执行。读者读完后,将完整掌握 Carbon 的 C++ 命名约定、语法格式化偏好、初始化与传址规则、auto使用策略,以及它在 LLVM/Clang 生态下的库选择与异常处理立场,可作为向 Carbon 提交 C++ 代码或理解其工具链源码风格的直接参考。
提案背景:为什么 Carbon 需要一份 C++ 风格指南
Carbon 语言的参考实现(toolchain)主体由 C++ 编写,仓库中toolchain/目录下的 driver、lex、parse、check、lower、sem_ir 等模块均为 C++ 源码。提案在 Problem 一节指出三个核心诉求:
- 一致性:保持所有 C++ 代码风格统一;
- 易学性:让新人容易上手、规则边界清晰;
- 降低评审成本:避免在代码评审中反复争论同样的风格与惯用法问题。
提案在 Background 一节系统调研了当时业界主流的多份 C++ 编码规范,包括 C++ Coding Standards、C++ Core Guidelines、Chromium C++ style guide、Google C++ Style Guide、JSF-AV(Joint Strike Fighter Air Vehicle C++ Coding Standards)、LLVM Coding Standards、Mozilla C++ Style Guide、WebKit Code Style Guidelines 等。这为"选取一个成熟基线而非从零发明"的决策提供了充分背景。
核心提案:Google 风格基线 + Carbon 本地化增补
提案的立场非常明确:以 Google C++ style guide 为基线,做最小、聚焦的增补与调整。理由包括:
- Google 风格指南在 Google 之外同样被广泛使用,Mozilla 与 Chromium 均以其为基线;
- 它既全面又具体,覆盖面广、基础扎实;
- 大多数情况下"主动偏离基线"的成本高于收益,尤其是在机械格式化之外的领域,偏离需要审慎论证。
在此基础上,Carbon 只选择能带来直接收益的少数分歧点:
- 命名约定:Google 风格的命名约定因历史代码库兼容需求而偏复杂,Carbon 没有历史包袱,且希望通过实践检验 Carbon 语言自身的命名方案。因此提案以 docs/project/cpp_style_guide.md 中的 Carbon 命名规则替换 Google 建议的命名约定。
- 多个允许选项中选定其一:Google 指南中不少"允许多种写法"的选项,其存在只是为了兼容既有代码库。Carbon 可以借此收紧规则,获得更一致、更现代的样式。详见风格指南中的 Carbon-local guidance 一节。
Carbon 本地化指导细则(落地解读)
提案最终在 docs/project/cpp_style_guide.md 中落地为可操作的规则。以下逐节展开。
命名规则:贴近 Carbon 语言自身的命名方案
Carbon 的 C++ 代码刻意向 Carbon 语言的命名约定靠拢,以便在实践中熟悉这套约定;恰巧它与 Google 风格相当接近,主要作用是简化。
- 已知编译期常量使用
UpperCamelCase(引用专有名词),包括:命名空间、类型名、函数、成员函数(下述例外除外)、模板参数、constexpr变量、枚举值等。 - 虚成员函数必须使用
UpperCamelCase:虚函数与非虚函数在调用点应无差别可见,因为是否为虚函数属于内部实现细节,命名不应随其改动。 - 成员函数可退化为
snake_case的条件:仅当其行为仅为返回数据成员的引用(或set_方法赋值),或其行为(含性能特征)对假设其为简单访问器的调用者来说毫不意外时。 - 其余一切名称使用
snake_case,包括函数参数、非常量的局部变量与成员变量;私有成员变量需加尾缀_。 - 缩写与首字母缩略词的写法:一般遵循 Google 风格(如
Api而非API),例外是LLVM与IR保持大写。 - 工具链缩写表:常用缩写维护在 toolchain/docs/idioms.md 的"abbreviations used in the code (AKA Carbon abbreviation decoder ring)"小节,例如
Inst(instruction)、Expr(expression)、Decl(declaration)、SemIR(semantics intermediate representation)等,注释中一般不用缩写。
这些命名规则在.clang-tidy中被显式配置为readability-identifier-naming.*检查(如ClassCase: CamelCase、VariableCase: lower_case、ClassMemberCase: lower_case等),即通过 clang-tidy 自动强制。
变量命名:_id后缀与类型消歧
风格指南给出典型实践:ID 类型变量通常加_id后缀;必要时用完整类型名消歧。例如同一实体存在ClassId、InstId、TypeId时,可命名为class_id、class_inst_id、class_type_id;一个Inst可叫class_inst。
这一约定与 toolchain/docs/idioms.md 中的 Index types 一节呼应:IndexBase/IdBase是int32_t的小型包装(toolchain/base/index_base.h),_id后缀表明变量对应某个IdBase;数组形式的 ID 集合用_block后缀或复数表示(如param_refs)。
文件命名
- 文件、目录、构建系统规则一律
snake_case,并避免使用-; - 源文件统一使用
.cpp扩展名(开源社区最常见的写法,也与"不加标点的 C++"书写习惯一致)。仓库中common/、toolchain/下的 C++ 实现均遵循此规则,例如 common/check.h、toolchain/check/check.cpp。
语法与格式化细则
风格指南针对"每个选项都合理、只需选一个保持一致"的细节给出了明确选择,并尽可能用clang-format强制执行:
- 一律使用尾返回类型语法(
-> ReturnType),包括-> void,以与 Carbon 语法保持一致; - 指针
*紧贴类型:TypeName* variable_name; - 一次只声明一个变量(多变量声明需要重复类型的一部分,易读性差);
- 外层
const写在类型之前:const int N = 42;; - 用
using类型别名而非typedef; - 禁止用
using引入std、llvm、clang等命名空间的非限定查找(如using std::vector;),理由是为了获得更清晰的诊断并避免 ADL 引起的歧义;例外是std::swap这类有意借助 ADL 调用的函数,写法为{ using std::swap; swap(thing1, thing2); }; - 构造函数一律
explicit,除非有明确理由支持隐式或{}初始化; - 条件、
switch、循环语句一律加大括号,即使主体只有一条语句;switch中case标签后按需加花括号建立作用域;除空循环体外,左花括号后立即换行; - 内部链接优先用
static而非匿名命名空间(static最小化读者感知内部链接所需的上下文);类与枚举仍须用匿名命名空间;测试代码例外——应放在被测代码命名空间之下的匿名命名空间中; - 访问控制:针对
.cpp文件中的 test fixture,使用public而非protected,这是由misc-non-private-member-variables-in-classestidy 检查所驱动的。
行注释:为 Carbon 注释语法"自举试吃"
风格指南要求只用//行注释,且独占一行,例外是参数名注释、命名空间收尾注释等结构性注释;尤其禁止把对某行代码的注释追加到该行行尾:
int bad = 42; // Don't comment here. // Instead comment here. int good = 42; // Closing namespace comments are structural, and both okay and expected. } // namespace MyNamespace这样做的三重收益:为 Carbon 规划的注释语法"吃自己的狗粮";形成单一一致的放置规则;对自动化重构更具韧性——重构常使代码变长、单行跨多行,导致行尾注释无处安放,而独立成行的前置注释在重构中仍不易混淆。
初始化语法:按"能否编译"分层的规则
初始化规则的依据是"什么能编译",与 Abseil Tip #88 略有差异:
- 直接以目标值(或直接指定该值的花括号初始化器)初始化时用赋值语法
=; - 聚合初始化优先用花括号:结构体、
std::pair、初始化列表等。结构体尽量用指定初始化器{.a = 1}(但 pair/tuple 不用);仅在必须编译时才带类型名WizType{.a = 1}——这与 Carbon 代码中结构体/元组的写法类似。对定义了构造函数的类型避免花括号初始化,但llvm::SmallVector<int> v = {0, 1};这类初始化列表、std::pair、std::tuple除外;永远不要对auto用花括号列表(auto a = {0, 1}是被禁止的); - 其余多数情况优先括号初始化
FooType foo(10);; - 不带
=的花括号初始化(BarType bar{10})作为兜底,仅在其他构造函数语法无法编译时使用。
传递地址:引用优先,指针用于两种情况
向函数传递对象地址时,除非以下情形之一,否则一律使用引用:
- 参数可选:用指针,并文档化其可能为空;
- 地址被捕获且必须活得比调用表达式更久:用指针,并文档化其非空(除非同时可选)。
存储非持有的成员地址时同样优先指针,例如风格指南给出的示例:
class Bar { public: // `foo` must not be null. explicit Bar(Foo* foo) : foo_(foo) {} private: Foo* foo_; };auto的使用策略
多数局部变量在类型可推断时使用auto,但基本类型(如bool、int)例外。auto并非强制,像SemIR::InstId这类较短类型名有时仍会显式写出——当类型比较晦涩、无法靠变量名说明时,显式命名类型是有帮助的。函数参数一般显式写出类型;lambda 参数在有益时可使用auto。这与 toolchain/docs/idioms.md 中 ValueStore 的用法建议一致:从ValueStore取返回值时常依赖存储类型名隐含返回类型而使用auto。
可复制与可移动类型
- 类型应具备值语义,尽量同时支持移动与拷贝;
- 无法拷贝的类型也应尽量支持移动;
- 若支持移动,移动应尽可能高效。
静态与全局变量:一律constexpr
无论文件、类还是函数作用域,全局和静态变量都应声明为constexpr。仓库中这类模式的落地示例可参见 toolchain/docs/idioms.md 的 "Defining constants usable in constexpr contexts" 一节(如类内静态常量配合static constexpr auto ComputeMyTable()计算器、以及toolchain/lex/lex.cpp中的IsIdStartByteTable全局常量表)。
基础库与数据类型:工具链优先 LLVM
工具链代码中优先使用 LLVM 库与数据结构而非标准 C++ 版本,原因有二:
- LLVM 容器针对无异常处理、无安全约束的编译器等高性能场景做过显著优化;
- 可以减少与 LLVM/Clang API 对接时的"词汇类型摩擦"。
同时,不得向可能成为编译器或运行时组成部分的代码引入其他第三方库依赖——编译器与运行库有独特的许可约束,项目希望这些层的所有传递依赖都处于 LLVM 许可(与 Carbon 项目整体一致)之下。这也解释了为何提案在备选方案中否定了 LLVM 编码标准却仍深度拥抱 LLVM 库。
迭代算法优先
风格指南明确选择迭代而非递归算法,并用 clang-tidy 辅助强制。原因在于 Carbon 预期被用于"压力测试编译器"的代码库(API 复杂交互、代码生成器产生的重复结构等)。Clang 靠近栈上限时启动线程的做法有性能开销且依赖开发者正确识别潜在递归,因此 Carbon 选择迭代方案以获得性能与稳健性。仓库中common/下的array_stack.h、growing_range.h等数据结构正是为迭代式实现提供支撑的基础设施。
工具化执行:.clang-format与.clang-tidy
风格指南的落地文档强调"尽可能用工具强制执行,减少作者与评审者的负担"。仓库根目录的两个配置文件就是这一理念的直接产物。
.clang-format:格式化基线
.clang-format 以BasedOnStyle: Google为基线,再做 Carbon 本地化收紧:
PointerAlignment: Left——指针*紧贴类型(对应指南"*靠类型");AllowShortBlocksOnASingleLine: false、AllowShortIfStatementsOnASingleLine: Never、AllowShortLoopsOnASingleLine: false、InsertBraces: true——强制单行块与大括号;QualifierOrder: [inline, static, friend, constexpr, const, volatile, restrict, type]、QualifierAlignment: Custom——统一限定符顺序;IfMacros/StatementMacros/Macros——为CARBON_DEFINE_RAW_ENUM_CLASS、CARBON_KIND_SWITCH、CARBON_ASSIGN_OR_RETURN、CARBON_KIND等宏提供格式化支持,其中CARBON_ASSIGN_OR_RETURN(x)=x让 clang-format 能"看穿"包含变量声明的宏。
.clang-tidy:静态检查收紧
.clang-tidy 默认启用bugprone-*、google-*、misc-*、modernize-*、performance-*、readability-*六大类检查,并设置WarningsAsErrors: '*'(配合bazel build --config=clang-tidy使用)。同时按风格决策显式关闭了一批与项目选择冲突的检查,例如:
-misc-use-anonymous-namespace、-readability-static-definition-in-anonymous-namespace——对应"内部链接优先static";-modernize-use-designated-initializers——对应"仅对结构体使用指定初始化器";-modernize-avoid-c-arrays、-performance-enum-size、-readability-magic-numbers等按工程成本评估后关闭。
CheckOptions 中还直接编码了命名与格式化决策:
readability-identifier-naming.*Case系列:类型/结构体/联合体/模板参数/typedef/命名空间/类常量一律CamelCase,成员变量/参数/普通变量一律lower_case(NamespaceIgnoredRegexp: '^(clang|llvm)$'允许为前置声明重新打开 LLVM/Clang 命名空间);misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: true——与"test fixture 用 public"的决策配套;modernize-use-trailing-return-type.TransformLambdas: none——与"lambda 不必写返回类型"一致。
备选方案分析:提案中的权衡
提案在 Alternatives considered 一节记录了被否定的方案及其理由,这些权衡最终沉淀为风格指南的立场。
在 Google 基线之上做不同变体
使用异常(exceptions):被否决。虽然更贴近标准 C++、Boost 等社区,但需要强异常安全保证的数据结构有显著性能问题;且 LLVM 本身不使用异常、不异常安全。该立场也被 toolchain/docs/idioms.md 的 C++ 方言一节延续——工具链不使用异常、虚基类与 RTTI,并与common/check.h中CARBON_CHECK/CARBON_DCHECK/CARBON_FATAL这类显式检查宏相配合。
函数参数每行一个:被判定为"大概率不值得投入时间争论的 bikeshed"。优点是编辑与作者不易写错、重构时 diff 最小、水平节奏规律;缺点是偏离 Google 风格较大,且参数过多导致的可读性问题更适合用分解(提取变量或 option struct)解决,而非靠格式化。
*与&靠变量:同样被标注为 bikeshed。优点是更贴近语言文法、声明与使用语法平行(int *p;中*p类型为int)、支持多变量声明;缺点是多数人习惯把*视为类型的一部分——但这一直觉无法推广到数组和函数指针等结构。
const后置:bikeshed。优点是多层const与指针/引用交替时更易读(但用类型别名避免多层const往往更佳);缺点是无指针声明中const <type> <id>;更传统,且这类声明占多数。
采用 LLVM 编码标准
提案明确否定了直接采用 LLVM Coding Standards:
- LLVM 规范在多处不够精确、具体,把大量问题留给评审者对照既有代码库自行判断,对 Carbon 不切实际;且 LLVM/Clang 自身并未始终一致地遵循其标准;
- 向 LLVM 项目贡献参考实现的设想可能性低且至少一年以上,风格匹配的顾虑被最小化;
- LLVM 当时尚未采用 C++17(受限于仍需 C++14 的既有用户),而 Carbon 无此约束,可以采纳更现代的 C++ 版本。
为什么最终要这份规则:Rationale 与后续影响
提案的 Rationale 一节总结了最终收益:
- 聚焦更重要的事:一份共同且成熟的风格指南让开发者与评审者把精力放在更重要的编码与评审问题上;
- 一致性带来可读性:熟悉规则后,一致的应用使代码库更易读;
- 可自动化:能被 clang-format 自动化的规则带来更高效的开发流程——这一点已由根目录的 .clang-format 与 .clang-tidy 兑现;
- 与 Carbon 语言词法规则对齐:例如注释语法与大小写规则,使工具链中 C++ 与 Carbon 两部分代码可以使用大体一致的风格——C++ 的尾返回类型、
UpperCamelCase/snake_case划分、{.a = 1}指定初始化器,都与 docs/design 中 Carbon 语言的语法方向呼应,也与此后语言设计文档的 设计风格指南 形成配套体系。
从提案到落地,p000113 为 Carbon 工具链的 C++ 代码确立了"Google 基线 + 最小化 Carbon 本地化增补"的风格框架;开发者若想为仓库贡献 C++ 代码,直接阅读 C++ 风格指南,并以 .clang-format 与 .clang-tidy 作为可执行约束即可快速对齐。
【免费下载链接】carbon-langCarbon Language's main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考