引言

引言 — The Rust Style Guide

译文 · 基于 The Rust Style Guide

原文链接: https://doc.rust-lang.org/nightly/style-guide/index.html

引言

动机:为何使用格式化工具

格式化代码基本上是一项机械性工作,既耗费时间,也消耗心力。借助自动格式化工具,程序员可以从这项任务中解脱出来,把精力集中在更重要的事情上。

此外,遵循既定的风格指南(例如本指南),程序员就不必临时拟定风格规则,也不必与其他程序员争论该采用何种风格规则,从而节省时间、沟通成本和心力。

人类通过模式匹配来理解信息。确保所有 Rust 代码都采用相近的格式,理解新项目所需的心力就会减少,从而降低新开发者的入门门槛。

因此,使用格式化工具(例如 rustfmt)能带来生产力上的收益;而采用社区一致的格式(通常通过使用格式化工具的默认设置)收益更大。

默认的 Rust 风格

《Rust 风格指南》定义了默认的 Rust 风格,并建议开发者和工具遵循该默认风格。rustfmt 等工具以本风格指南作为默认风格的参考。本指南中的全部内容——无论是否使用「必须」之类的措辞,或「插入一个空格……」或「在……之后换行」这样的祈使语气——均指默认风格。

这不应被理解为禁止开发者采用非默认风格,或禁止工具添加任何特定的配置选项。

缺陷

若本风格指南与 rustfmt 不一致,可能是 rustfmt 的缺陷,也可能是风格指南的缺陷;无论哪种情况,请向 style 团队或 rustfmt 团队(或两者)报告,以便调查和修复。

若基于本风格指南和默认 Rust 风格实现新的格式化工具,请在现有的 Rust 代码语料上测试,并避免造成大范围破坏。此类工具的实现与测试可能会暴露风格指南或 rustfmt 中的缺陷,以及工具自身的缺陷。

我们通常以避免大范围破坏的方式来解决缺陷。

格式约定

缩进与行宽

  • 使用空格,不要使用制表符。
  • 每一级缩进必须为 4 个空格(即,字符串字面量和注释之外的所有缩进必须是 4 的倍数)。
  • 一行的最大宽度为 100 个字符。

块缩进

优先使用块缩进,而非视觉缩进:

1
2
3
4
5
6
7
8
9
// 块缩进
a_function_call(
    foo,
    bar,
);

// 视觉缩进
a_function_call(foo,
                bar);

这样可以得到更小的 diff(例如,若将上面示例中的 a_function_call 重命名),并减少向右漂移。

尾随逗号

在任何种类的逗号分隔列表中,若其后紧跟换行,则使用尾随逗号:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function_call(
    argument,
    another_argument,
);

let array = [
    element,
    another_element,
    yet_another_element,
];

这样更便于移动代码(例如通过复制粘贴),也能得到更小的 diff,因为追加或删除项时无需修改另一行来添加或删除逗号。

空行

项与语句之间用零行或一行空行分隔(即一个或两个换行)。例如:

1
2
3
4
5
6
7
8
9
fn foo() {
    let x = ...;

    let y = ...;
    let z = ...;
}

fn bar() {}
fn baz() {}

行尾空白

任何行的末尾都不要包含行尾空白。这包括空行、注释行、代码行和字符串字面量。

注意,在字符串字面量中避免行尾空白时需小心,以保持字面量的值不变。

排序

在多种情况下,默认的 Rust 风格会规定对某些内容进行排序。若未另行说明,此类排序应为「版本排序」,以确保(例如)x8 排在 x16 之前,尽管字符 1 排在字符 8 之前。

就 Rust 风格而言,要按版本排序比较两个字符串:

  • 将两个字符串从头到尾处理为两列最大长度的块,其中每个块要么由非 ASCII 数字的字符序列组成,要么由 ASCII 数字序列组成(数字块),然后比较两字符串中对应的块。
  • 比较两个数字块时,按数值比较,忽略前导零。若两块数值相等但前导数字个数不同,且这是这两字符串第一次出现此种情况,则将这两块视为相等(继续比较下一块),但记住哪一个字符串有更多的前导零。
  • 若两块都不是数字块,则按 Unicode 字符的字典序比较,有两处例外:
    • _(下划线)紧排在 (空格)之后、任何其他字符之前。(这将下划线视为词分隔符,与标识符中的常见用法一致。)
    • 除非另行说明,版本排序应将非小写字符(可出现在 UpperCamelCase 标识符开头的字符)排在小写字符之前。
  • 若比较进行到字符串末尾,且每一对块都被视为相等:
    • 若某次数字比较记录了最早出现「一个字符串的前导零比另一个多」的位置,则将前导零更多的字符串排在前面。
    • 否则,两字符串相等。

注意,存在多种被称为「版本排序」的算法,它们通常试图解决同一问题,但在诸多方面有所不同(例如对带前导零的数字的处理)。本算法并不声称精确匹配任何特定其他算法的行为,只求为 Rust 格式化产生简单且令人满意的结果。具体而言,本算法旨在:对前导零个数相同的一组符号产生令人满意的结果;对前导零个数不同的一组符号产生可接受且易于理解的结果。

例如,版本排序会将下列字符串按给定顺序排列:

  • _ZYXW
  • _abcd
  • A2
  • ABCD
  • Z_YXW
  • ZY_XW
  • ZY_XW
  • ZYXW
  • ZYXW_
  • a1
  • abcd
  • u_zzz
  • u8
  • u16
  • u32
  • u64
  • u128
  • u256
  • ua
  • usize
  • uz
  • v000
  • v00
  • v0
  • v0s
  • v00t
  • v0u
  • v001
  • v01
  • v1
  • v009
  • v09
  • v9
  • v010
  • v10
  • w005s09t
  • w5s009t
  • x64
  • x86
  • x86_32
  • x86_64
  • x86_128
  • x87
  • zyxw

模块级项

语句

表达式

类型

注释

以下关于注释的指南仅为建议,机械格式化工具可能会跳过对注释的格式化。

优先使用行注释(//),而非块注释(/* ... */)。

使用行注释时,在起始标记之后放一个空格。

使用单行块注释时,在起始标记之后和结束标记之前各放一个空格。对于多行块注释,在起始标记之后换行,并在结束标记之前换行。

优先将注释单独成行。若注释跟在代码之后,在其前面放一个空格。若块注释以内联形式出现,则按标识符或关键字的方式处理其周围空白。

示例:

1
2
3
4
5
6
// 项上的注释。
struct Foo { ... }

fn foo() {} // 项后的注释。

pub fn foo(/* 参数前的注释 */ x: T) {...}

注释通常应为完整句子。以大写字母开头,以句号(.)结尾。内联块注释可视为不带标点的备注。

整行都是注释的源码行,其长度应限制为 80 个字符(含注释标记,但不含缩进)或该行的最大宽度(含注释标记和缩进),取两者中较小者:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// This comment goes up to the ................................. 80 char margin.

{
    // This comment is .............................................. 80 chars wide.
}

{
    {
        {
            {
                {
                    {
                        // This comment is limited by the ......................... 100 char margin.
                    }
                }
            }
        }
    }
}

文档注释

优先使用行注释(///),而非块注释(/** ... */)。

优先使用外部文档注释(/// 或 /** ... */),仅在编写模块级或 crate 级文档时使用内部文档注释(//! 和 /*! ... */)。

将文档注释放在属性之前。

属性

将每个属性单独成行,缩进到与项相同的层级。 对于内部属性(#!),缩进到项内部的层级。在可能的情况下,优先使用外部属性。

对于带参数列表的属性,按函数的方式格式化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#[repr(C)]
#[foo(foo, bar)]
#[long_multi_line_attribute(
    split,
    across,
    lines,
)]
struct CRepr {
    #![repr(C)]
    x: f32,
    y: f32,
}

对于带等号的属性,在 = 前后各放一个空格,例如 #[foo = 42]。

必须只有一个 derive 属性。供工具作者注意:若将多个 derive 属性合并为一个属性,为正确性起见,派生名称的顺序一般必须保留:#[derive(Foo)] #[derive(Bar)] struct Baz; 必须格式化为 #[derive(Foo, Bar)] struct Baz;。

小型 项

本指南多处规定的格式取决于某段代码构造是否小型。例如,单行与多行结构体字面量:

1
2
3
4
5
6
7
8
// 常规格式
Foo {
    f1: an_expression,
    f2: another_expression(),
}

// "小型" 格式
Foo { f1, f2 }

我们把小型的确切含义留给各个工具自行决定。特别地,工具可以在不同情形下使用不同的定义。

一些合适的启发式包括项的大小(以字符计)或项的复杂度(例如,所有组成部分必须是简单名称,而非更复杂的子表达式)。关于合适启发式的更多讨论,见该 issue。

非格式约定

Cargo.toml 约定

制定这些指南所用的原则

最后修改 August 21, 2026: 更新 (76fc81a2e)