Rust-编码规范

  1. 一、命名
    1. 1.1 在一个crate中,保证标识符的命名规则使用统一词序
    2. 1.2 为crate feature命名时不应有无意义的词占用
    3. 1.3 标识符命名应该符合阅读习惯
    4. 1.4 作用域越大命名越精确,反之应简短
    5. 1.5 用于访问或获取数据的 getter 类方法通常不要使用 get_ 前缀
    6. 1.6 遵循 iter/ iter_mut/ into_iter 规范来生成迭代器
    7. 1.7 避免使用语言内置保留字、关键字、内置类型和trait等特殊名称
    8. 1.8 避免在变量的命名中添加类型标识
    9. 1.9 定义全局静态变量时需加前缀XXX以便和常量有所区分
    10. 1.10 使用统一的命名风格
      1. 1.10.5 变量命名规范
      2. 1.10.1 蛇形命名法
      3. 1.10.2 大驼峰命名法
      4. 1.10.3 其它
      5. 1.10.4 说明
  2. 1.11 类型转换函数命名需要遵循所有权语义
  3. 1.12 过程宏的包名规范
  • 二、注释
    1. 2.1 文档要干练简洁
    2. 2.2 注释应该有宽度限制
    3. 2.3 使用行注释而避免使用块注释
    4. 2.4 文件头注释包含版权说明
    5. 2.5 在注释中使用 TODO 来帮助任务协作
    6. 2.6 在公开的返回Result类型的函数文档中增加 Error 注释
    7. 2.7 如果公开的API在某些情况下会发生Panic,则相应文档中需增加 Panic 注释
    8. 2.8 在文档注释中要使用空格代替 tab
  • END 附录
    1. END.1 开发环境
    2. END.2 语法提示工具
    3. END.3 工具链安装
    4. END.4 Rust版本
    5. END.5 包管理
    6. END.6 常用Cargo插件
      1. END.6.1 Clippy
      2. END.6.2 Rustfmt
      3. END.6.3 Rustfix
      4. END.6.4 Cargo Edit
      5. END.6.5 Cargo Deny
      6. END.6.6 Crago Make
  • END.7 测试
  • END.8 术语解释
  • END.9 推荐优化
  • END.10 使用的 crate 及版本
  • 来源:[规范]https://rust-coding-guidelines.github.io/rust-coding-guidelines-zh/overview.html

    一、命名

    1.1 在一个crate中,保证标识符的命名规则使用统一词序

    词并不重要,而是保证词序的一致性。即使是类似的功能。

    例如:

    JoinPathsError
    ParseBoolError
    ParseCharError
    ParseFloatError
    ParseIntError
    RecvTimeoutError
    StripPrefixError

    上面为动词-宾语-error的顺序命名。如果想新增错误类型,应该是ParseAddrError而不是AddrParseError

    1.2 为crate feature命名时不应有无意义的词占用

    比如使用abc命名来替代 use-abc 或 with-abc。并且 Cargo 要求 features 应该是相互叠加的,所以像 no-abc 这种负向的 feature 命名实际上并不正确。

    [features]
    // 不符合
    default = ["use-std"]
    std = []
    // 不符合
    no-abc=[]

    1.3 标识符命名应该符合阅读习惯

    命名保证简洁明了,容易理解,增加代码的高可读性。
    一些好的实践包括但不限于:

    • 使用正确的英文单词并符合英文语法,不要使用拼音
    • 仅使用常见或领域内通用的单词缩写
    • 布尔型变量或函数避免使用否定形式,双重否定不利于理解
    • 不要使用 Unicode 标识符
    // 不符合: 使用拼音
    let ming: &str = "John";
    let xing: &str = "Smith";
    // 不符合: 含义不明确
    const ERROR_NO_1: u32 = 336;
    const ERROR_NO_2: u32 = 594;
    // 不符合:函数名字表示的函数作用不明了
    fn not_number(s:&str) -> bool {/* ... */}

    1.4 作用域越大命名越精确,反之应简短

    • 对于全局函数、全局变量、宏、类型名、枚举命名,应当精确描述并全局唯一。
    • 对于函数局部变量,或者结构体、枚举中的成员变量,在其命名能够准确表达含义的前提下,应该尽量简短,避免冗余信息重复描述。
    // 不符合:描述不精确
    static GET_COUNT: i32 = 42; // static MAX_THREAD_COUNT: i32 = 42;

    // 不符合:信息冗余
    enum WebEvent { // enum WebEvent {
    PageLoadEvent,
    PageUnloadEvent,
    KeyPressEvent(char),
    PasteEvent(String),
    ClickEvent { x: i64, y: i64 },
    }

    // 不符合:信息冗余
    type MaskSize = u16; // type Size = u16;
    pub struct HeaderMap {
    mask: MaskSize,
    }

    1.5 用于访问或获取数据的 getter 类方法通常不要使用 get_ 前缀

    因为 Rust 所有权语义的存在,此例子中两个方法的参数分别是共享引用 &self 和独占引用 &mut self,分别代表了 getter 的语义。
    也存在一些例外情况可以用 get_ 前缀。

    pub struct First;
    pub struct Second;

    pub struct S {
    first: First,
    second: Second,
    }

    impl S {
    // 不符合:访问成员函数名字不用get_前缀。
    pub fn get_first(&self) -> &First {
    &self.first
    }

    // 不符合:
    // 同样不建议 `get_mut_first`, or `mut_first`.
    pub fn get_first_mut(&mut self) -> &mut First {
    &mut self.first
    }

    // set_前缀是可以的
    pub fn set_first(&mut self, f: First) -> &mut First {
    self.first = f;
    }
    }

    正例
    pub struct First;
    pub struct Second;

    pub struct S {
    first: First,
    second: Second,
    }

    impl S {
    // 符合
    pub fn first(&self) -> &First {
    &self.first
    }

    // 符合
    pub fn first_mut(&mut self) -> &mut First {
    &mut self.first
    }

    // set_前缀是可以的
    pub fn set_first(&mut self, f: First) {
    self.first = f;
    }
    }

    例外
    只有当需要显式的语义来通过getter获取某种数据,才会使用get命名。例如,Cell::get可以访问一个Cell的内容。

    对于进行运行时验证的getter,例如边界检查,可以考虑添加一个 Unsafe 的_unchecked 配套方法。

    // 进行一些运行时验证,例如边界检查
    fn get(&self, index: K) -> Option<&V>;
    fn get_mut(&mut self, index: K) -> Option<&mut V>;
    // 没有运行时验证,用于在某些情况下提升性能。比如,在当前运行环境中不可能发生越界的情况。
    unsafe fn get_unchecked(&self, index: K) -> &V;
    unsafe fn get_unchecked_mut(&mut self, index: K) -> &mut V;

    特别是rust语法中的getter和类型转换区别很小,有些时候不是很清晰。

    1.6 遵循 iter/ iter_mut/ into_iter 规范来生成迭代器

    对于容纳 U 类型的容器 ,其迭代器方法应该遵循iter/ iter_mut/ into_iter这三种命名方式。
    返回的迭代器类型名称也应该和其方法名保持一致,如一个叫做into_iter()的方法应该返回一个叫做IntoIter的类型。

    例外
    标准库中存在一个例外: str 类型是有效 UTF-8 字节的切片(slice),概念上与同质集合略有差别,所以 str 没有提供 iter/iter_mut/into_iter 命名的迭代器方法,而是提供 str::bytes 方法来输出字节迭代器、 str::chars 方法来输出字符迭代器。

    1.7 避免使用语言内置保留字、关键字、内置类型和trait等特殊名称

    命名必须要避免使用语言内置的保留字、关键字、内置类型和trait等特殊名称。

    1.8 避免在变量的命名中添加类型标识

    不需要在变量命名中添加关于类型的标识。

    let account_bytes: Vec<u8> = read_some_input();   // 不符合:account 的类型很清楚,没必要在命名中加 `_bytes`
    let account_str = String::from_utf8(account_bytes)?; // 不符合:account 的类型很清楚,没必要在命名中加 `_str`
    let account: Account = account_str.parse()?; // 不符合:account 的类型很清楚,没必要在命名中加 `_str`

    1.9 定义全局静态变量时需加前缀XXX以便和常量有所区分

    为了提升代码可读性和可维护性,有必要将常量的命名和全局静态变量加以区分。

    static G_EVENT: [i32;5]=[1,2,3,4,5];
    const MAGIC_NUM: i32 = 65 ;

    1.10 使用统一的命名风格

    Rust 倾向于在类型级的结构中使用驼峰命名风格,在 变量、值(实例)、函数名等结构中使用蛇形命名风格。
    很多语言都会提供代码格式化工具和 linter 来消灭这类缺陷。Rust 有内置的 cargo fmtcargo clippy 来帮助开发者统一代码风格,来避免常见的开发错误。

    1.10.5 变量命名规范

    Rust变量名可以由字母,数字或者下划线组成。同时还有以下3个限制条件:

    • 不能以数字开头
    • 字母区分大小写
    • 不能只有下划线
    1.10.1 蛇形命名法
    • 文件名:例如:hello_world.rs、main_xxx.rs
    • 变量名:例如:zhangsan_name
    • 函数名/方法名:例如:func_name()
    • 模块名:例如:test_tokio {}
    • 特性:Features
    1.10.2 大驼峰命名法
    • 结构体:例如:struct ExampleStruct { name: String}
    • enum类型:例如:enum IpAddress {IPV4(u8,u8,u8,u8)}
    • Trait类型:例如:trait Summer {}
    • type类型:例如:type SummerInt = u8
    1.10.3 其它
    • 关联常量:全部大写,例如:NAME、AGE
    • 全局变量:全部大写
    • 语句:跟C,Java语言等一样,每行语句结束都要添加;
    • 通用构造函数:new 或者 init
    • 转换构造函数:from_some_other_type
    • 生存期:简短的字符串即可,尽量保持语义
    • 不建议以-rs_rs为后缀来命名包名
    1.10.4 说明

    在大驼峰命名法下,缩略语、复合词算作一个单词,如应该使用 Uuid 而非 UUID,使用 Usize 而不是 USize,或者是 Stdin 而不是 StdIn
    在蛇形命名、常量命名下,每个词不应该由单个单词组成。比如,使用 btree_map 而不使用 b_tree_map,使用 PI_2 而不使用 PI2

    1.11 类型转换函数命名需要遵循所有权语义

    进行特定类型转换的方法名应该包含以下前缀:

    名称前缀 内存代价 所有权
    as_ 无代价 borrowed -> borrowed
    to_ 代价昂贵 borrowed -> borrowed、borrowed -> owned (非 Copy 类型)、owned -> owned (Copy 类型)
    into_ 看情况 owned -> owned (非 Copy 类型)

    as_into_ 作为前缀的类型转换通常是降低抽象层次 ,要么是查看背后的数据(as) ,要么是分解(deconstructe)背后的数据(into)。相对来说,以 to_ 作为前缀的类型转换处于同一个抽象层次,但是底层会做更多工作,比如多了内存拷贝等操作。
    如:当一个类型用更高级别的语义封装一个内部类型时,应该使用 into_inner() 方法名来取出被封装类型的值。

    如果类型转换方法返回的类型具有 mut 修饰,那么这个方法的名称应如同返回类型组成部分的顺序那样,带有 mut 。如 Vec::as_mut_slice 返回 &mut [T] 类型,这个方法的功能正如其名称所述,所以这个名称优于 as_slice_mut

    1.12 过程宏的包名规范

    TODO

    二、注释

    注释分为两类:

    • 普通注释使用 ///* ... */
    • 文档注释使用 /////!/** ... **/

    描述注释则为普通注释,描述文档则为文档注释

    2.1 文档要干练简洁

    注释固然重要,但是代码其实就是文档本身。高可读的类型名、函数名和变量名, 要远胜过要用注释解释的含糊不清的名字。
    不要描述显然易见的代码。

    注释内容始终围绕两个关键点来构建:

    • What : 用于阐述代码为了什么而实现。
    • how : 用于阐述代码如何去使用。

    Rust 项目文档应该始终基于 rustdoc 工具来构建,rustdoc 支持 Markdown 格式,为了使得文档更加美观易读,文档注释应该使用 Markdown 格式。
    模块级文档://!
    普通文档注释示例:///

    2.2 注释应该有宽度限制

    2.3 使用行注释而避免使用块注释

    尽量使用/////,而非块注释。
    对于文档注释,仅在编写模块级文档时使用 //!,在其他情况使用 ///更好。

    2.4 文件头注释包含版权说明

    文件头(即,模块级)注释应先包含版权说明。如果文件头注释需要增加其他内容,可以在版权说明下面补充。
    可以包括

    • 文件功能说明。
    • 作者。
    • 创建日期 和 最后修改日期。
    • 注意事项。
    • 开源许可证(比如, Apache 2.0, BSD, LGPL, GPL)。
    • 其他。

    2.5 在注释中使用 TODO 来帮助任务协作

    使用rust的TODO来完成协作开发。

    2.6 在公开的返回Result类型的函数文档中增加 Error 注释

    pub的返回Result类型的函数文档中,建议增加 # Error 注释来解释什么场景下该函数会返回什么样的错误类型,方便用户处理错误。

    #![warn(clippy::missing_errors_doc)]

    fn main() {
    use std::io;
    // 符合:增加了规范的 Errors 文档注释

    /// # Errors
    ///
    /// Will return `Err` if `filename` does not exist or the user does not have
    /// permission to read it.
    pub fn read(filename: String) -> io::Result<String> {
    unimplemented!();
    }

    2.7 如果公开的API在某些情况下会发生Panic,则相应文档中需增加 Panic 注释

    pub函数文档中,建议增加 # Panic 注释来解释该函数在什么条件下会 Panic,便于使用者进行预处理。

    #![warn(clippy::missing_panics_doc)]

    // 符合:增加了规范的 Panic 注释
    /// # Panics
    ///
    /// Will panic if y is 0
    pub fn divide_by(x: i32, y: i32) -> i32 {
    if y == 0 {
    panic!("Cannot divide by 0")
    } else {
    x / y
    }
    }

    2.8 在文档注释中要使用空格代替 tab

    提倡使用四个空格代替tab,在文档注释中也应该统一使用四个空格。

    END 附录

    END.1 开发环境

    • Visual Studio Code
    • CLion或者IDEA(这里需要注意版本,因为低版本可能会导致无法安装到最新的rust语法插件)

    END.2 语法提示工具

    • Rust Analyzer
    • Rust Language Server(rls)

    目前推荐Rust Analyzer

    END.3 工具链安装

    推荐使用rustup

    END.4 Rust版本

    TODO

    END.5 包管理

    Cargo 是 Rust 项目必不可少的包管理器。
    Cargo 通过 Cargo.toml 配置文件来管理 crate。

    END.6 常用Cargo插件

    END.6.1 Clippy

    Clippy 是一个静态分析工具,它提供了很多检查,比如错误、 样式、 性能问题、 Unsafe UB问题等等。从1.29版本开始,Clippy可以用于 Stable Rust中。

    END.6.2 Rustfmt

    Rustfmt 是一个根据风格指南原则来格式化代码的工具。

    END.6.3 Rustfix

    从 Rust 2018 edition开始,Rustfix就被包含在 Rust 中。它可以用来修复编译器警告。

    END.6.4 Cargo Edit

    Cargo Edit插件为Cargo扩展了三个命令:

    • Cargo add,在命令行增加新的依赖,而不需要去知道这个依赖的语义版本。
    • Cargo rm,在命令行删除一个指定依赖。
    • Cargo upgrade,在命令行升级一个指定依赖。
    END.6.5 Cargo Deny

    该插件可以检测依赖中的软件许可证(License),如果和开发者配置的不符合,则会拒绝使用该依赖。

    END.6.6 Crago Make

    Rust任务运行器和构建工具。

    END.7 测试

    TODO

    END.8 术语解释

    TODO

    END.9 推荐优化

    TODO

    END.10 使用的 crate 及版本

    • tokio:网络及高性能IO
    • futures:异步
    • serde:序列化
    • syn:对 TokenStream 解析的库
    • quote:把代码转换成可以操作的数据
    • anyhow/thierr:错误处理