Rust 语言风格指南

动机——为什么要使用格式化工具?

格式化代码是一项耗时耗力的机械性工作。如果使用自动格式化工具,开发者就可以从这项工作中解脱出来,专注于更重要的事情。

此外,通过坚持使用既定的风格指南(如本指南),开发者无需制定特别的风格规则,也无需与其他开发者争论应使用何种样式规则,从而节省了时间、沟通成本和精神耗损。

人类通过模式匹配来理解信息。通过确保所有 Rust 代码具有相似的格式,就能减少理解新项目所需的脑力劳动,从而降低新开发人员的入门门槛。

因此,使用格式化工具(如 rustfmt)可以提高工作效率,而使用社区一致的格式规约(通常是使用格式化工具的默认设置)则会带来更大的好处。

默认 Rust 风格

《Rust 语言风格指南》定义了默认 Rust 风格,并建议开发者和工具遵循默认 Rust 样式。rustfmt 等工具使用此风格指南作为默认风格的参考。本风格指南中的所有内容,无论是否使用“必须”等语言或“插入空格......”或“在......后换行”等命令式语气,都是指默认样式。

这不应被解释为禁止开发人员遵循非默认样式,或禁止工具添加任何特定的配置选项。

格式约定

缩进和行宽

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

块缩进

与视觉化缩进(visual indent)相比,更倾向于分块缩进:

// 块缩进
a_function_call(
    foo,
    bar,
);

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

这样做的差异就会变小(例如,在上例中重命名了a_function_call),向右移动的情况也会减少。

尾逗号

在任何类型的逗号分隔列表中,如果后面有换行符,请使用尾逗号:

function_call(
    argument,
    another_argument,
);

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

这使得移动代码(例如通过复制和粘贴)变得更容易,并使差异更小,因为添加或删除项目不需要修改另一行来添加或删除逗号。

空行

不用空行或一个空行(即 1 或 2 个换行符)分隔程序项和语句。例如:

fn foo() {
    let x = ...;

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

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

模块级别的程序序项

语句

表达式

类型

注释

以下关于注释的指导原则仅为建议,机器格式化工具可能会跳过注释格式化。

行注释 (//) 优先于块注释 (/* ... */)。

使用行注释时,在开头符号后留一个空格。

使用单行块注释时,在开头符号后和结尾符号前各留一个空格。对于多行块注释,在开头符号后加一个换行符,在结尾符号前加一个换行符。

注释最好独立成行。如果注释紧跟代码,则在注释前空格一个。如果块注释出现在行内,则使用周围的空格,就像使用标识符或关键字一样。不要在注释后或多行注释中任何一行的末尾使用拖尾空格。例如:

// 程序项中的注释。
struct Foo { ... }

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

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

注释通常应是完整的句子。英文的注释开头用大写字母,结尾用句点(.)(译注:若是使用中文注释,则换成中文标点符号)。内联块注释可视为不带标点符号的注释。

完全是注释的源文件行长度应限制在 80 个字符以内(包括注释符号,但不包括缩进),或该行的最大宽度(包括注释符号和缩进),以较小者为准:

// This comment goes up to the ................................. 80 char margin.
// 该注释的边距为 ............................. 80 字符。

{
    // This comment is .............................................. 80 chars wide.
    // 此注释宽 ............................................................. 80 字符。
}

{
    {
        {
            {
                {
                    {
                        // This comment is limited by the ......................... 100 char margin.
                        // 此注释受 ................................................ 100 字符边距的限制。
                    }
                }
            }
        }
    }
}

文档注释

优先使用行注释 (///) 而不是块注释 (/** ... */)。

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

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

属性

每个属性放在单独一行,跟程序项保持一致的缩进。如果是内部属性 (#!),则缩进到程序项内部的位置。尽可能使用外属性。

对于带有参数列表的属性,格式应与函数类似。

#[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;

For attributes with an equal sign, put a single space before and after the =, e.g., #[foo = 42].

简短程序项

在本指南的许多地方,我们指定的格式取决于代码结构的简短。例如,单行结构文字与多行结构文字:

// 正常格式化
Foo {
    f1: an_expression,
    f2: another_expression(),
}

// “简短”格式化
Foo { f1, f2 }

我们让各个工具自行决定“简短”的确切含义。特别是,在不同的情况下,工具可以自由使用不同的定义。

一些合适的启发式方法是程序项的大小(以字符为单位)或程序项的复杂程度(例如,所有组件必须是简单的名称,而不是更复杂的子表达式)。有关合适的启发式方法的更多讨论,请参考此讨论问题

非格式化约定

Cargo.toml 的约定

决定这些准则的原则