Rust 编程规范 是一套关于如何编写清晰、一致、可维护和高效 Rust 代码的指导原则。遵循这些规范不仅能提升代码库的整体质量,还能促进团队成员之间的协作,减少潜在错误,并充分利用 Rust 语言在内存安全和并发方面的优势。本规范融合了 Rust 官方《Rust 程序设计语言》、rustfmt 的默认风格以及社区的普遍最佳实践。

核心思想:通过统一的风格、明确的结构和对语言特性的恰当应用,提高代码的可读性、可维护性和安全性,最终提升开发效率和软件质量。


一、命名规范 (Naming Conventions)

Rust 的命名约定遵循了其标准库和社区的惯例,有助于快速理解代码元素的类型和目的。

1.1 snake_case (蛇形命名法)

所有字母小写,单词之间用下划线 _ 连接。

  • 变量 (Variables)
    1
    2
    let file_name = "data.txt";
    let mut item_count = 0;
  • 函数 (Functions)
    1
    fn calculate_area(width: f64, height: f64) -> f64 { /* ... */ }
  • 方法 (Methods)
    1
    2
    3
    impl MyStruct {
    fn get_current_status(&self) -> &str { /* ... */ }
    }
  • 模块 (Modules)
    1
    mod network_utils;
  • 字段 (Struct Fields)
    1
    2
    3
    4
    struct User {
    first_name: String,
    last_name: String,
    }

1.2 PascalCase (帕斯卡命名法 / 大驼峰命名法)

每个单词的首字母大写,不使用分隔符。

  • 类型 (Types)
    • 结构体 (Structs)
      1
      2
      3
      struct UserProfile {
      // ...
      }
    • 枚举 (Enums)
      1
      2
      3
      4
      5
      enum ConnectionState {
      Connected,
      Disconnected,
      Connecting,
      }
    • Trait (特性)
      1
      2
      3
      trait Serializable {
      // ...
      }
  • 泛型类型参数 (Generic Type Parameters):通常使用单个大写字母,或描述性的 PascalCase
    1
    2
    3
    4
    5
    6
    struct Cache<K, V> { // K, V 是泛型类型参数
    // ...
    }
    fn process_data<T: Clone + Debug>(item: T) { // T 是泛型类型参数
    // ...
    }

1.3 SCREAMING_SNAKE_CASE (尖叫蛇形命名法)

所有字母大写,单词之间用下划线 _ 连接。

  • 常量 (Constants)
    1
    const MAX_BUFFER_SIZE: usize = 1024;
  • 静态变量 (Static Variables)
    1
    static APP_VERSION: &str = "1.0.0";
  • 枚举变体,当它们没有关联数据且行为像常量时 (不强制,但常见):
    1
    2
    3
    4
    5
    6
    7
    enum LogLevel {
    DEBUG,
    INFO,
    WARN,
    ERROR,
    }
    // 但如果包含数据,则通常使用 PascalCase,如 Message::Quit

1.4 生命周期 (Lifetimes)

通常使用单引号 ' 后跟小写字母,最常见的是 'a'static 是一个特殊的生命周期。

1
2
3
4
fn get_longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() { s1 } else { s2 }
}
let static_str: &'static str = "This has a static lifetime.";

二、格式化规范 (Formatting Conventions)

一致的代码格式可以显著提高代码的可读性,并降低维护成本。Rust 官方提供了格式化工具 rustfmt

2.1 强制使用 rustfmt

  • 原则:始终使用 rustfmt 来自动化代码格式化,以此确保整个项目乃至整个组织的代码风格一致。
  • 命令行
    • 格式化当前项目:cargo fmt
    • 检查代码是否已格式化(用于 CI/CD):cargo fmt -- --check
  • 配置rustfmt 的行为可以通过 rustfmt.toml 文件进行配置,但除非有强烈理由,否则建议使用默认配置。

2.2 常见格式化规则 (由 rustfmt 默认执行)

  • 缩进 (Indentation)
    • 使用 4 个空格进行缩进,而不是制表符。
  • 行长 (Line Length)
    • 每行代码通常不超过 100 个字符rustfmt 会自动在合理的位置进行换行。
  • 花括号 (Braces)
    • K&R 风格:左花括号 { 与声明语句在同一行。
    1
    2
    3
    4
    5
    6
    7
    8
    fn my_function() {
    if condition {
    // ...
    }
    }
    struct MyStruct {
    // ...
    }
  • 空白 (Whitespace)
    • 操作符周围应有空格:a + bx == y
    • 逗号和分号后应有空格:func(arg1, arg2);let x = 5;
    • 类型注解 :应有空格:let x: u32 = 5;
    • 函数参数和泛型参数之间fn foo<T, U>(arg1: &T, arg2: U)
  • 逗号 (Trailing Commas)
    • 在多行列表(函数参数、struct 字段、enum 变体、match 分支、数组元素等)的最后一个元素后建议保留一个逗号。这有助于版本控制系统 (VCS) 的差异分析,并简化添加/删除元素。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    fn some_function(
    param1: u32,
    param2: &str,
    param3: bool, // 尾随逗号
    ) { /* ... */ }

    struct Config {
    port: u16,
    host: String,
    timeout: u64, // 尾随逗号
    }
  • 空行 (Blank Lines)
    • 使用空行分隔不同的函数、方法、impl 块以及函数内部的主要逻辑块,以提高可读性。
    • use 声明组之间也可以用空行分隔。

三、语句规范 (Statement Conventions)

本节涵盖了关于 Rust 语言结构和控制流的编写风格和用法。

3.1 变量和可变性 (Variables and Mutability)

  • 默认不可变:优先使用 let 声明不可变变量。
  • 明确可变性:只在确实需要修改变量时才使用 let mut
    1
    2
    3
    let initial_value = 10; // 不可变
    let mut counter = 0; // 可变
    counter += 1;
  • 最小化可变作用域mut 关键字影响的是绑定本身,而不是值。当一个值不再需要可变时,应将其重新绑定为不可变,或让其可变引用离开作用域。

3.2 所有权与借用 (Ownership and Borrowing)

  • 优先借用:函数参数和返回值应优先使用引用 (&T&mut T) 而非所有权转移 (T),以避免不必要的数据复制和堆分配。
    1
    2
    3
    fn process_ref(s: &str) { /* ... */ } // 借用不可变引用
    fn modify_ref(s: &mut String) { /* ... */ } // 借用可变引用
    fn consume_string(s: String) { /* s 获得所有权 */ }
  • 避免不必要的 clone()clone() 会导致堆内存分配和数据复制,影响性能。只有当你确实需要一个独立且具有所有权的数据副本时才使用。
  • 借用检查器规则:在任何给定作用域内,只能有:
    • 一个可变引用 (&mut T),或者
    • 任意数量的不可变引用 (&T)。
      始终遵守这些规则,Rust 编译器会强制执行。

3.3 返回值 (Return Values)

  • 表达式作为返回值:函数体末尾的表达式(不带分号)是隐式返回值。这在 Rust 中非常常见且推荐用于简洁的函数。
    1
    2
    3
    fn add_one(x: i32) -> i32 {
    x + 1 // 隐式返回
    }
  • 显式 return:在需要提前退出函数或在复杂逻辑中提高清晰度时使用 return 关键字。
    1
    2
    3
    4
    5
    6
    fn divide(numerator: i32, denominator: i32) -> Option<i32> {
    if denominator == 0 {
    return None; // 提前退出
    }
    Some(numerator / denominator)
    }

3.4 错误处理 (Error Handling)

  • Result<T, E> 用于可恢复错误:这是处理预期错误(如文件未找到、网络连接失败)的标准方式。
    • ? 运算符:优先使用 ? 运算符来简洁地传播 Result 错误。只能在返回 ResultOption 的函数中使用。
      1
      2
      3
      4
      5
      6
      fn read_file_content(path: &str) -> std::io::Result<String> {
      let mut file = std::fs::File::open(path)?; // 传播错误
      let mut content = String::new();
      file.read_to_string(&mut content)?;
      Ok(content)
      }
    • match 表达式:当需要根据不同的错误类型执行不同的处理逻辑时。
    • 自定义错误类型:使用自定义枚举来表示 Crate 内的特定错误,并实现 std::error::Error Trait。thiserror Crate 可以大大简化此过程。
  • panic! 用于不可恢复错误:当程序进入一种无法处理的、不应该发生的状态(如数组越界、逻辑错误)时,使用 panic! 宏。
    • 避免 unwrap()expect():在库或生产代码中,避免直接使用 Result::unwrap()Option::unwrap()。因为它会在 Err/None 时直接 panic!。如果必须在特定情况下使用,expect() 会提供更友好的 panic 消息。
    • unwrap() / expect() 适用于测试、示例代码、或在开发初期快速迭代。

3.5 流程控制 (Control Flow)

  • if / else if / else:用于基于条件执行代码。
    • 可以用作表达式返回值。
    • 条件不需要括号。
    1
    let x = if condition { 5 } else { 6 };
  • match 表达式
    • 用于对一个值与一系列模式进行穷尽性比较。
    • 穷尽性:编译器会强制 match 匹配所有可能的情况,或使用通配符 _ 来处理剩余情况。
    • 解构:可用于解构枚举、结构体、元组。
    • 守卫 (Guards):在模式中使用 if 条件细化匹配。
  • if let / while let:用于简洁地处理只关心一个特定模式而忽略其他情况的场景。
    1
    2
    3
    4
    5
    6
    7
    if let Some(value) = result_option {
    // ...
    }

    while let Some(item) = my_vec.pop() {
    // ...
    }
  • 循环
    • for 循环:优先使用 for 循环遍历迭代器,它是最安全和最符合习惯的循环方式。
      1
      2
      for item in &my_vec { /* ... */ }
      for i in 0..10 { /* ... */ }
    • while 循环:当需要基于一个不断变化的条件进行循环时。
    • loop 循环:创建无限循环,通常与 breakreturn 结合使用。loop 也可以返回一个值。

3.6 类型注解 (Type Annotations)

  • Rust 类型推断:Rust 编译器通常可以推断类型,无需显式注解。
  • 明确性优先:在以下情况应添加类型注解:
    • 函数签名(参数和返回值)必须有类型注解。
    • 当类型推断不明确,或代码可读性会因此受益时。
    • 在定义常量 const必须有类型注解。
    1
    2
    3
    let inferred_string = "hello".to_string(); // 类型推断为 String
    let explicit_u32: u32 = 42; // 显式注解
    const PI: f64 = 3.14159; // 常量必须注解

3.7 宏 (Macros)

  • 充分利用标准库宏:如 vec!, format!, println!, dbg!, assert!, matches!, unreachable! 等。它们通常提供比手动实现更安全、更简洁或更高效的替代方案。
    • dbg! 宏在调试时极其有用,它会打印表达式的值及其文件名和行号。
  • 自定义宏:只在通过函数和泛型无法实现必要抽象时才考虑编写声明宏 (macro_rules!) 或过程宏。

四、注释与文档规范 (Comments and Documentation Conventions)

良好的注释和文档对于代码的可理解性和可维护性至关重要,尤其是在 Rust 这样拥有严格安全检查的语言中。

4.1 文档注释 (/////!)

  • 目的:用于描述公共 API(包括 Crate、模块、函数、结构体、枚举、Trait、方法等),供 rustdoc 工具生成文档。
  • 语法
    • /// (外层文档注释):用于描述紧跟在它后面的项。最常用。
    • //! (内层文档注释):用于描述包含它的整个项(例如,一个 lib.rs 或一个模块文件)。
  • 内容
    • 第一行摘要:简短地总结该项的功能,以句号结尾。rustdoc 会将其展示为摘要。
    • 详细描述:随后的段落可以提供更深入的解释、使用场景、限制等。
    • Markdown 支持:文档注释支持 Markdown 语法,可以用于格式化文本、包含代码示例。
  • 标准文档部分 (Markdown 标题)
    • # Examples:提供演示如何使用该项的代码示例。rustdoc 会自动测试这些代码示例,确保它们是有效的。
    • # Panics:说明该函数可能在哪些情况下 panic!
    • # Errors:如果函数返回 Result,列出可能的 Err 变体及其含义。
    • # Safety:如果函数是 unsafe 的,详细说明其不安全的原因、调用者必须满足的不变条件 (invariants) 和前置条件 (preconditions)。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//! # My Awesome Crate
//!
//! `my_awesome_crate` is a library for performing various awesome operations.
//!
//! # Examples
//!
//! ```
//! use my_awesome_crate::Calculator;
//!
//! let mut calc = Calculator::new();
//! calc.add(5);
//! assert_eq!(calc.get_total(), 5);
//! ```

/// A simple calculator that stores a running total.
///
/// # Fields
/// - `total`: The current sum of operations.
pub struct Calculator {
total: i32,
}

impl Calculator {
/// Creates a new `Calculator` instance with a total of 0.
///
/// # Examples
///
/// ```
/// let calc = my_awesome_crate::Calculator::new();
/// assert_eq!(calc.get_total(), 0);
/// ```
pub fn new() -> Self {
Calculator { total: 0 }
}

/// Adds `value` to the calculator's `total`.
///
/// # Panics
///
/// This method will panic if the addition overflows an `i32`.
///
/// # Examples
///
/// ```
/// let mut calc = my_awesome_crate::Calculator::new();
/// calc.add(10);
/// ```
pub fn add(&mut self, value: i32) {
self.total = self.total.checked_add(value).expect("Addition overflowed!");
}

/// Returns the current total.
pub fn get_total(&self) -> i32 {
self.total
}
}

4.2 普通注释 (//)

  • 目的:用于解释非公共的代码、复杂或不明显的逻辑、临时的代码或需要解决的问题。
  • 避免冗余:不要注释那些从代码中一眼就能看出来的东西。
  • 何时使用
    • 解释为什么选择某种方法,而不是解释它做了什么。
    • 解释复杂算法的关键步骤。
    • 标记待办事项或已知问题。
  • 标记:使用 TODO:FIXME:HACK: 等标准标记来表示需要后续处理的地方。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn process_incoming_data(data: &[u8]) {
// 解密数据块,这是一个复杂的 AES-256 操作
// TODO: 考虑使用硬件加速进行解密
let decrypted_data = decrypt(data);

// 数据验证,确保其符合预期的格式
if !validate_format(&decrypted_data) {
// FIXME: 更详细的错误处理和日志记录
eprintln!("Invalid data format received.");
return;
}

// 将处理后的数据存储到数据库,这里使用了一个缓存层来提高性能
// HACK: 当前缓存策略可能在内存不足时导致问题
store_to_database(&decrypted_data);
}

五、其他建议 (Other Recommendations)

5.1 使用 Clippy

  • 功能Clippy 是一个 Rust linter,提供了大量有用的 lint 规则,用于发现常见的错误、改进代码风格和提高代码性能。
  • 用法:在项目中运行 cargo clippy。建议在 CI/CD 流程中强制执行 clippy 检查。

5.2 模块组织 (Module Organization)

  • 逻辑分组:将相关功能分组到模块中。
  • 一个模块一个文件:当模块变得复杂时,将其内容移动到单独的文件中(src/mod_name.rs)或目录中(src/mod_name/mod.rs)。
  • use 声明
    • use 声明放在文件的顶部,通常按标准库、第三方库、Crate 内部的顺序分组。
    • 优先导入具体项 (use std::collections::HashMap;) 而非整个模块 (use std::collections;),以避免命名冲突。
    • 使用 as 关键字解决命名冲突。
    • 避免使用 * 导入所有项,除非在测试模块中或作为 Prelude。

5.3 可见性 (Visibility)

  • 默认私有化:Rust 中所有的项默认都是私有的。这是一种强大的封装机制,应充分利用。
  • 最小化 pub 暴露:只将公共 API 标记为 pub
  • 限定可见性
    • pub(crate):仅在当前 Crate 内可见。用于实现 Crate 内部的私有细节。
    • pub(super):仅在父模块中可见。
    • pub(in path):在指定路径下可见。

5.4 性能考量

  • 零开销抽象:Rust 的高层抽象(如迭代器)通常会编译成与手写低级代码一样高效。优先使用它们,而不是进行不必要的“过度优化”。
  • 避免不必要的 clone():堆分配和数据复制有性能成本。
  • 切片 (&[], &str) 优先于 Vec/String:作为函数参数时,如果不需要修改所有权或数据,传递切片更高效。
  • 基准测试:不要过早优化。使用 Criterion Crate 或其他基准测试工具来定位性能瓶颈。

5.5 结构体构造 (Struct Initialization)

  • 字段初始化简写:当函数参数名与结构体字段名相同时,可以使用简写。
    1
    2
    3
    4
    5
    6
    7
    8
    fn create_user(username: String, email: String) -> User {
    User {
    username, // 等同于 username: username,
    email, // 等同于 email: email,
    active: true,
    sign_in_count: 1,
    }
    }
  • 结构体更新语法:从现有实例创建新实例时使用 .. 语法。
    1
    2
    3
    4
    5
    let user1 = User { email: String::from("..."), username: String::from("..."), active: true, sign_in_count: 1 };
    let user2 = User {
    email: String::from("another@example.com"),
    ..user1 // 复制 user1 的其他字段
    };

总结

遵循 Rust 编程规范是编写高质量代码的关键一步。它不仅能提高代码的可读性和可维护性,还能让团队成员(包括未来的你)更容易理解和贡献代码。通过持续使用 rustfmt 进行格式化,clippy 进行代码检查,以及在开发过程中积极采纳这些最佳实践,你将能够构建出更健壮、更高效、更安全的 Rust 应用程序。