Overview

《Rust 编码规范》 中文版

WIP.See the Chinese version for the full version, the English version is currently being translated from the Chinese version, welcome to participate in the contribution!

Lists

Introduce

It is understood that some companies and organizations within the Rust community maintain their own coding specifications. Some of the publicly available ones are listed below.

The role of coding specifications

  1. Improve the readability, maintainability, robustness, and portability of code by following Rust language features.
  2. Improve the standardization and security of Unsafe Rust code writing.
  3. The programming specification terms strive to systematize, easy to apply, easy to check, to help developers improve development efficiency.
  4. Give developers a clear and global vision, in the process of developing code can follow a good code specification, rather than wait until the code is written and then through rustfmt and clippy such tools, a line to modify the warning.
  5. The specification is not a tutorial, but the level of developers varies. For some knowledge blind spots and those that may lead to program errors, the specification will also cover.

1.1 Why you need the Rust coding specification ?

When I first learned Rust, I marveled at the sophistication of the tools Rust provides. For example, rustfmt, which automatically formats code, and clippy, which helps you standardize code that is not written properly. They are truly excellent tools. At the time, I didn't think Rust needed to be coded at all like other languages.

But as I learned more and more about Rust, I came to realize that these tools had a lot of shortcomings and did not cover everything. For example, improper configuration and use of rustfmt can lead to code errors and inability to recognize the semantics of various naming in Rust code; clippy has some false positives or lint is not reasonable, and can not cover to Unsafe Rust and other issues. Developers, especially newcomers, if they rely on rutfmt and clippy for a long time like using a black box, but do not understand the reason behind their lint, just know the reason but not the reason. it is impossible to improve the development efficiency under the premise that the code quality has certain requirements.

So, rutfmt and clippy are not a panacea. We also need a comprehensive and universal coding specification that also covers tools like rustfmt and clippy, so that the majority of Rust teams can quickly implement Rust and enhance collaboration and trust between teams by standardizing the principles and rules to understand the basic framework for writing authentic Rust code.

Limitations of Rustfmt and Clippy

Limitations of Rustfmt

Rust has an automatic formatting tool, rustfmt, which helps developers get rid of the manual work of formatting code and increase productivity. However, it does not replace the coding specification to standardize the coding style of Rust code.

The main drawbacks of rustfmt are the following.

  1. The naming of variables, types, functions, etc. in Rust contains semantics, especially ownership semantics. rustfmt tool cannot determine the naming semantics in the code. This aspect can be partially satisfied by using Clippy, but it is rather one-sided for developers.
  2. rustfmt can cause problems if used improperly or configured improperly. Because rustfmt is an auto-formatting tool, it automatically modifies the code, but it does not compile when it modifies the code. If the developer configures autosave and then executes rustfmt automatically, it will cause the code to be modified incorrectly. Or, there are some configuration options for rustfmt that are misconfigured, which will also cause the code to be modified incorrectly.
  3. The configuration items of rustfmt tool are fragmented, and most developers do not understand the meaning of each configuration item.
  4. rustfmt does not have a coding specification that covers code comments and documentation comments.

In summary, there is a particular need to provide a common coding specification so that developers clearly understand what coding style Rust follows overall in terms of naming, formatting and commenting. It will cover the content of rustfmt, but not mechanically extract the rules of rustfmt one by one, but a unified categorization and organization of the rules of rustfmt, to facilitate developers to understand the rules set in rustfmt, and to facilitate the team to form their own code style.

Clippy's limitations

Clippy is a linter for Rust, one of the main components of the Rust ecosystem. It performs additional static checks on developed code, reports problems found and explains how to fix them (sometimes it can even fix them automatically). Using it can be beneficial for Rust beginners and even professionals.

However, using Clippy does not mean that it can replace coding specifications, and it has many drawbacks:

  1. Unsafe Rust is a very important part of Rust and needs to be covered by a complete coding specification to help developers write safe Unsafe code.
  2. there are more than 500 lint in Clippy so far, and there is a growing trend. it is impossible for developers to understand each lint one by one, so a coding specification is needed to help developers to sort out and categorize lint.
  3. there is some controversy about the suggestion and classification (allow/warning/deny) of lint in Clippy. Some lint is allow by default, but it does not mean that it is reasonable to write in some scenarios; similarly, some lint is warning, but it does not mean that it is unreasonable in some scenarios. For this reason, dtolnay also created this repository: https://github.com/dtolnay/noisy-clippy for analyzing how many Clippy lint's in the community crate suggestions does not match the actual scenario, so as to achieve the goal of improving Clippy.

In summary, Clippy is a very useful tool, but it is not a replacement for coding specifications.

1.2 Basic conventions of the coding specification

Programming specifications are in no way written to increase the burden on developers and are intended to help developers write high-quality Rust code.

To accomplish this, the specification terms are divided into two categories: Principles and Rules.

  • A principle is a general direction that guides programming development, or refers to a class of situations. There are also a few principles that are detectable by the Rust compiler, but because the compiler diagnostic information is confusing, principles are added to help developers avoid such situations.

  • Rules, as opposed to principles, are more specific and contain positive and negative examples to further illustrate them. Some rules also add exceptions. The rules are basically detectable by lint.

Rule content Relationship with rustfmt and clippy

The specification is divided into two main parts: code style and code practice.

Code style

Code naming, formatting and comments are included in the code style.

  • The naming part, mainly by clippy lint to check, some naming rules clippy lint does not provide detection, then need to custom lint to support.
  • The format part, mainly using rustfmt to automatically modify, the rules in the coding specification describes most of the configuration items of rustfmt by category, in order to facilitate developers to make reference and develop their own configuration items. Configuration templates are also provided in the coding specification.
  • The comments section, which includes general comments and documentation comments, rule entries are regulated through a collaboration between rustfmt and clippy.

Code Practices

The code practices are categorized by Rust language features, and each language feature is summarized as much as possible for everyday coding best practices, extracted into a list of principles and rules for developers to refer to. Most of the rules are recommendations, and the rules that are required are basically security-related.

Clippy lint involves a lot of skillful lint, so it is not put into the specification.

The rules mainly focus on generic scenarios, code readability, maintainability, security, performance of the four considerations, it only covers a small part (less than 1/5) clippy lint. There are also rules that are not available in the clippy lint and require custom lint.

The focus of the code practice content is on the Unsafe Rust coding specification, which has more coding principles than rules, and which is rarely detected by Clippy lint. There are more rules that require classes.

We hope that this section will help developers avoid some common pitfalls in writing Rust code.

Coding specification content conventions

Identified by the number before the title.

  • Identified as P for Principle. Numbered as P.Element.Number.
  • Identified as G for Rule (Guideline). The numbering is G.Element.Number.
  • When there are subdirectories. Number is P.Element.SubElement.Number or G.Element.SubElement.Number.

Number is incremented from 01. Where Element is the three-letter abbreviation for the key element (corresponding to the secondary catalog in this specification) in the domain knowledge. (Terminology reference: SEI CERT C Coding Standard)

ElementExplanationElementExplanation
NAM命名 (Naming)CMT注释 (Comment)
FMT格式 (Format)TYP数据类型 (Data Type)
CNS常量 (Const)VAR变量 (Variables)
EXP表达式 (Expression)CTF控制流程 (Control Flow)
REF引用 (Reference)PTR指针 (Pointer)
STR字符串 (String)INT整数 (Integer)
MOD模块 (Module)CAR包管理 (Cargo)
MEM内存 (Memory)FUD函数设计 (Function Design)
MAC宏 (Macro)STV静态变量 (Static Variables)
GEN泛型 (Generic)TRA特质 (Trait)
ASY异步 (Async)UNS非安全 (Unsafe Rust)
SAS安全抽象 (Safety Abstract)FFI外部函数调用接口 ( Foreign Function Interface )
LAY内存布局 (Layout)ERR错误处理 (Error Handle)
CLT集合 (Collection)MTH多线程 (Multi Threads)
EMB嵌入式Rust (Embedded Rust)FIO输入输出 (In/Out)
SEC信息安全 (Security)SPT智能指针 (Smart Pointer)
UNT单元类型 (Unit)BOL布尔 (Bool)
CHR字符类型 (Char)FLT浮点数 (Float)
SLC切片类型 (Slice)TUP元组 (Tuple)
ARR固定长度数组类型 (Array)VEC动态长度数组 (Vector)
SCT结构体 (Struct)ENM枚举体 (Enum)
UNI联合体 (Union)BLN标准库内置(BuiltIn)
OBJTrait 对象 (Trait Object)LFT生命周期 (Lifetime)
BOXBox<T> 类型DRP析构函数 (Drop)
DCL声明宏 (Declarative)PRO过程宏 (Procedural)
LCK锁同步 (Lock)LKF无锁 (Lock Free)
OTH其他 (Ohters)

Reference code open source license description

All references to external code in this specification meet the MIT/Apache/Mozilla public licenses open source license!

Coding Style

2.1 Naming

A good naming style allows us to quickly understand what a name represents (type, variable, function, constant, macro, etc.) and even highlights its semantics in the overall code context. Naming management is quite important to improve the readability and maintainability of your code.

P.NAM.01 The naming convention for identifiers in the same crate should use a uniform word order

[Description]

The particular choice of word order is not important, but pay attention to consistency within the crate and consistency with similar functionality in the standard library.

For example:

If the naming of types in crate were in verb-object-error word order, we need to follow the same order when we adding new types.

Here are some error types from the standard library:

All of these use verb-object-error word order. If we were adding an error to represent an address failing to parse, for consistency we would want to name it in verb-object-error order like ParseAddrError rather than AddrParseError.

Note: The AddrParseError of module net in the standard library is an exception. Most of the error types from the standard library follow the verb-object-error word order.

[Bad Case]


#![allow(unused)]
fn main() {
// Bad:Not the "verb-object-error" order, should be `ParseAddrError`
struct AddrParseError {}
}

[Good Case]


#![allow(unused)]
fn main() {
// Good: The order is the same as the standard library
struct ParseAddrError{}
}

P.NAM.02 Names for cargo features should not contain meaningless placeholders

P.NAM.03 Identifier naming should be in line with reading habits

P.NAM.04 The larger the scope, the more precise the naming, and the opposite should be short

P.NAM.05 The getter family of methods used to access or retrieve data should not normally have the prefix get_

[Description]

Because of Rust's ownership semantics, both methods' parameters in following cases use shared reference &self or exclusive reference &mut self , which represent getter semantics.

getter semantics always mean to return an object but a reference in Rust's ownership conventions.

There are also some exceptions to use get_ prefix.

[Bad Case]


#![allow(unused)]
fn main() {
pub struct First;
pub struct Second;

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

impl S {
    // Bad: Shouldn't use 'get_' prefix to name accesssing member function.
    pub fn get_first(&self) -> &First {
        &self.first
    }

    // Bad:
    // `get_mut_first`, or `mut_first` are also not good.
    pub fn get_first_mut(&mut self) -> &mut First {
        &mut self.first
    }

    // set_ prefix is fine.
    pub fn set_first(&mut self, f: First) -> &mut First {
        self.first = f;
    }
}
}

[Good Case]


#![allow(unused)]
fn main() {
pub struct First;
pub struct Second;

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

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

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

    // set_ prefix is fine.
    pub fn set_first(&mut self, f: First) -> &mut First {
        self.first = f;
    }
}
}

[Exception]

However, there are also some exceptions: Only in cases to retrieve data by getter explicitly, could name with get_ prefix. For example, Cell::get could access the data in one Cell.

For getter checked in runtime, such as bounds checking, we could consider to add an Unsafe _unchecked methods. Generally, there would be following function signatures.


#![allow(unused)]
fn main() {
// Do some checks in runtime, such as bounds checking.
fn get(&self, index: K) -> Option<&V>;
fn get_mut(&mut self, index: K) -> Option<&mut V>;
// No runtime checking, use to improve performance in some case. 
// For example, when executed in an execution environment,which is impossible to trigger bounds checking.
unsafe fn get_unchecked(&self, index: K) -> &V;
unsafe fn get_unchecked_mut(&mut self, index: K) -> &mut V;
}

There is almost no such distinction between getter and type conversion (G.NAM.02). For instance, TemDir::path could be regarded as a getter, which represents the filesystem's path of temporary directory, and TemDir::into_path is in charge of sending converted data when deleting temporary directory to callee.

There would not result for redundancy with naming the method as path, because path itself is a getter, otherwise if you name it as get_path or as_path.

Implementations in StandardLibrary:

P.NAM.06 Follow the iter/ iter_mut/ into_iter specification to generate iterators

P.NAM.07 Avoid using special names such as language built-in reserved words, keywords, built-in types and traits

P.NAM.08 Avoid adding type identifiers to the naming of variables

P.NAM.09 Global static variables should be defined with the prefix G_ to distinguish them from constants

G.NAM.01 Use a uniform naming style

G.NAM.02 Type conversion function naming needs to follow ownership semantics

2.2 Format

A uniform coding style is developed to improve the readability of code and make it easier to maintain daily code and review code between teams.

Rust has an automated formatting tool, rustfmt, to help developers get rid of the manual work of formatting code and improve productivity. However, what style specification rustfmt follows, as developers need to understand, when writing code can be proactively written in such a style.

Description:

For unstable configuration items in rustfmt (Stable is No), it means that the configuration item cannot be changed in the stable (Stable) version of Rust, but its default value will take effect when cargo fmt. Under Nightly Rust, all configurations can be customized.

For information on how to use unstable configuration items in Stable Rust, sample configurations, and other global configuration item descriptions, see: Rustfmt Configuration Description.

Caution

Because the rustfmt tool automatically modifies code, to ensure that rustfmt does not accidentally change the wrong code, you should pay attention to the following two descriptions when using rustfmt: 1.

  1. Always make sure that the rustfmt command is executed after all the code has been modified and compiled. Because the code is not compiled during rustfmt execution, there is no static check protection. 2.

  2. If you are using an IDE or editor with automatic protection turned on, do not turn on automatic rustfmt execution.

P.FMT.01 Automatic code formatting with rustfmt

P.FMT.02 Use spaces instead of tabs for indentation

P.FMT.03 The maximum width of line spacing is one blank line

P.FMT.04 Language items should be defined with the left braces position on the same line

P.FMT.05 Block indentation should be maintained when multiple identifiers are present

P.FMT.06 When there is a multi-line expression operation, the operator should be placed at the beginning of the line

P.FMT.07 Both enumeration variants and structure fields should be left aligned

P.FMT.08 Line feeds for functions with more than five parameters or imported modules with more than four parameters

P.FMT.09 不同的场景,使用不同的空格风格

P.FMT.10 match 分支应该具有良好的可读性

P.FMT.11 导入模块分组应该具有良好的可读性

P.FMT.12 声明宏分支应该具有良好的可读性

P.FMT.13 具名结构体字段初始化时不要省略字段名

P.FMT.14 extern 外部函数需要显式指定 C-ABI

P.FMT.15 解构元组的时候允许使用..来指代剩余元素

P.FMT.16 不要将派生宏中多个不相关的特质合并为同一行

2.3 Annotations and Documentation

In Rust, there are two types of annotations: normal annotations and documentation annotations. Normal comments use // or /* ... */, and document comments use ///, //! or /** ... **/.

When referring to "comments" in the principles and rules, this includes both normal comments and document comments. When "documentation" is mentioned, it refers specifically to documentation comments.

Reference

  1. RFC 505: API Annotation Conventions
  2. RFC 1574: API Documentation Conventions
  3. Making Great Docs with Rustdoc
  4. Rust Doc book

P.CMT.01 The code can be self-annotated, and the documentation should be concise

[Description]

一、The code can be self-annotated, avoiding redundant ordinary code comments.

Comments are important, but the best code is documentation itself. Meaningful type, function, and variable names are far better than ambiguous names explained by comments. Comments are used when meaningful type names, function names, and variable names cannot express complete semantics.

Don't describe obvious phenomena, and never translate code in natural language as comments.

二、The documentation should be concise.

  1. The content and terms in the documentation comments should be as short and concise as possible, and should not be too long. Make sure your code is well commented and understandable by others. Good comments convey the context and purpose of the code.
  2. Comments are always structured around two key points:
    • What: Used to describe what the code implement.
    • How: Used to describe how to use code your code.
  3. The natural language used for comments and documentation comments should be consistent.
  4. Rust project documentation should always be built on the rustdoc tool. rustdoc supports the Markdown format. To make the documentation more beautiful and readable, the documentation comments should use the Markdown format.

[Good Case]

Module Based Documentation Comments. From Rust std std::vec:


#![allow(unused)]
fn main() {
// Good

//! # The Rust core allocation and collections library
//!
//! This library provides smart pointers and collections for managing
//! heap-allocated values.
//!
//! This library, like libcore, normally doesn’t need to be used directly
//! since its contents are re-exported in the [`std` crate](../std/index.html).
//! Crates that use the `#![no_std]` attribute however will typically
//! not depend on `std`, so they’d use this crate instead.
//!
//! ## Boxed values
//!
//! The [`Box`] type is a smart pointer type. There can only be one owner of a
//! [`Box`], and the owner can decide to mutate the contents, which live on the
//! heap.
}

General Documentation Comments. From Rust std Vec::new method.


#![allow(unused)]
fn main() {
// Good

/// Constructs a new, empty `Vec<T>`.
///
/// The vector will not allocate until elements are pushed onto it.
///
/// # Examples
///
/// ```
/// # #![allow(unused_mut)]
/// let mut vec: Vec<i32> = Vec::new();
/// ```
#[inline]
#[rustc_const_stable(feature = "const_vec_new", since = "1.39.0")]
#[stable(feature = "rust1", since = "1.0.0")]
pub const fn new() -> Self {
    Vec { buf: RawVec::NEW, len: 0 }
}
}

P.CMT.02 Comments should have a width limit

[Description]

The width of each comment line should not be too long and needs to be set at a certain width, no more than 120, which help improve readability.

In rustfmt, the comment_width with wrap_comments configuration item can automatically split comments that exceed the width limit into multiple lines.

Note: The use_small_heuristics configuration item of rustfmt does not include comment_width.

[Bad Case]


#![allow(unused)]
fn main() {
// Bad
// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
}

[Good Case]

When comment_width=80 and wrap_comments=true.

Note: Here wrap_comments does not use the default value and needs to be configured to true.


#![allow(unused)]
fn main() {
// Good
// Lorem ipsum dolor sit amet, consectetur adipiscing elit,
// sed do eiusmod tempor incididunt ut labore et dolore
// magna aliqua. Ut enim ad minim veniam, quis nostrud
// exercitation ullamco laboris nisi ut aliquip ex ea
// commodo consequat.
}

[rustfmt configuration]

ParameterOptionalstable or notDescription
comment_width80(default)NoSpecify the maximum width allowed for a line of comments
wrap_commentsfalse(default), true(suggestion)NoRunning multi-line comments automatically change to multi-line comments by maximum width

P.CMT.03 Use line comments and avoid block comments

[Description]

Try to use line comments (// or ///) rather than block comments. This is a convention in the Rust community.

For documentation comments, use //! , in other cases /// is better.

Note: #! [doc] and #[doc] have a special role in simplifying document comments, and it is not necessary to force them into //! or //.

[Bad Case]


#![allow(unused)]
fn main() {
// Bad

/*
 * Wait for the main task to return, and set the process error code
 * appropriately.
 */
mod tests {
    //! This module contains tests

    // ...
}
}

[Good Case]

When normalize_comments = true:


#![allow(unused)]
fn main() {
// Good

// Wait for the main task to return, and set the process error code
// appropriately.

// Good
// When defining modules using the `mod` keyword, 
// it is better to use `///` on top of `mod`.

/// This module contains tests
mod tests {
    // ...
}

// Good
#[doc = "Example item documentation"]
pub enum Foo {}
}

【rustfmt configuration】

ParameterOptionalStable or notDescription
normalize_commentsfalse(default) true(suggestionn)NoConvert the /**/ comment to /
normalize_doc_attributesfalse(default)NoConvert the #! [doc] and #[doc] annotations to //! and ///

P.CMT.04 File header comments include a copyright notice

[Description]

File header (i.e., module-level) comments should include the copyright notice first. If additional content needs to be added to the file header comments, it can be added under the copyright notice.

May include:

  1. A description of the function of the document.
  2. Author.
  3. Creation date and last modification date.
  4. Notes.
  5. Open source license (e.g., Apache 2.0, BSD, LGPL, GPL).
  6. Other.

The format of the copyright notice is as follows:

  • 中文版:版权所有(c)XXX 技术有限公司 2015-2022
  • English version: Copyright (c) XXX Technologies Co. Ltd. 2015-2022. All rights reserved. Licensed under Apache-2.0.

Its content can be adjusted to participate in the following detailed description:

  • 2015-2022 can be modified as needed. 2015 is the year the file was first created and 2022 is the year the file was last modified. It is possible to write only one creation year, so that the copyright notice does not need to be changed if the file is modified frequently.
  • For internal use, there is no need to add All rights reserved.
  • Licensed under Apache-2.0., if it is open source then you can add the license statement.

Caution when writing copyright notes:

  • Copyright notes should be written from the top of the file header.
  • The header comment should contain the "copyright notice" first, followed by the rest of the content.
  • Optional content should be added as needed to avoid empty formatting without content.
  • Maintain uniform formatting, either by the project or by the larger context.
  • Maintain a neat layout, with line breaks aligned.

[Good Case]


#![allow(unused)]
fn main() {
// Good
// 版权所有(c)XXX 技术有限公司 2015-2022。

// Or

// Good
// Copyright (c) XXX Technologies Co.Ltd. 2015-2022. 
// All rights reserved. Licensed under Apache-2.0.
}

P.CMT.05 Use FIXME and TODO in comments to help with task collaboration

[Description]

Collaboration can be facilitated by turning on FIXME and TODO in the comments.

Note: This entry is not suitable for use with rustfmt related configuration items report_fixme and report_todo, which have been removed in rustfmt v2.0.

[Good Case]


#![allow(unused)]
fn main() {
// Good
// TODO(calebcartwright): consider enabling box_patterns feature gate
fn annotation_type_for_level(level: Level) -> AnnotationType {
    match level {
        Level::Bug | Level::Fatal | Level::Error => AnnotationType::Error,
        Level::Warning => AnnotationType::Warning,
        Level::Note => AnnotationType::Note,
        Level::Help => AnnotationType::Help,
        // FIXME(#59346): Not sure how to map these two levels
        Level::Cancelled | Level::FailureNote => AnnotationType::Error,
        Level::Allow => panic!("Should not call with Allow"),
    }
}
}

G.CMT.01 Error comments need to added the documentation of functions that return Result types in the public

[Level] Suggestion

[Description]

In the public (pub) documentation of functions that return Result types, it is recommended to add # Error comments to explain what scenarios the function will return what kind of error type, so that users can easily handle errors.

Description: This rule can be detected by cargo clippy, but it does not warn by default.

[Bad Case]


#![allow(unused)]
#![warn(clippy::missing_errors_doc)]

fn main() {
use std::io;
// Bad: Clippy will be warned "warning: docs for function returning `Result` missing `# Errors` section"
pub fn read(filename: String) -> io::Result<String> {
    unimplemented!();
}
}

[Good Case]


#![allow(unused)]
#![warn(clippy::missing_errors_doc)]

fn main() {
use std::io;
// Good:adding normatives Errors documentation comments

/// # 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!();
}
}

[Lint detection]

Lint nameClippy detectableRustc detectableLint GroupDefault level
missing_errors_doc yesnoStyleallow

G.CMT.02 If a Panic occurs under certain circumstances in the public API, add a Panic comment to the corresponding document

[Level] Requirements

[Description]

In the public (pub) function documentation, it is recommended to add # Panic comments to explain under what conditions the function will Panic, so that users can pre-process it.

Description: This rule is detected by cargo clippy. It does not warn by default.

[Bad Case]


#![allow(unused)]
#![warn(clippy::missing_panics_doc)]

fn main() {
// Bad:If do not add Panic relevant comments.`Clippy` will raise error: "warning: docs for function which may panic missing `# Panics` section"。
pub fn divide_by(x: i32, y: i32) -> i32 {
    if y == 0 {
        panic!("Cannot divide by 0")
    } else {
        x / y
    }
}
}

[Good Case]


#![allow(unused)]
#![warn(clippy::missing_panics_doc)]

fn main() {
// Good:Added Panic comments
/// # 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
    }
}
}

[Lint detect]

Lint nameClippy detectableRustc detectableLint GroupDefault level
missing_panics_doc yesnoStyleallow

Default is allow,But this pricipal need to be set #![warn(clippy::missing_panics_doc)]

G.CMT.03 Use spaces in document comments instead of tabs

[Level] Suggestion

[Description]

Rust code style promotes the use of four spaces instead of tabs, and four spaces should be used consistently in the documentation comments.

[Bad Case]

Tab is used in the following document comments.


#![allow(unused)]
fn main() {
// Bad:Tab indentation is used in document comments
///
/// Struct to hold two strings:
/// 	- first		one
/// 	- second	one
pub struct DoubleString {
   ///
   /// 	- First String:
   /// 		- needs to be inside here
   first_string: String,
   ///
   /// 	- Second String:
   /// 		- needs to be inside here
   second_string: String,
}
}

[Good Case]


#![allow(unused)]
fn main() {
// Good:Four spaces indentation is used in document comments
///
/// Struct to hold two strings:
///     - first        one
///     - second    one
pub struct DoubleString {
   ///
   ///     - First String:
   ///         - needs to be inside here
   first_string: String,
   ///
   ///     - Second String:
   ///         - needs to be inside here
   second_string: String,
}
}

[Lint detect]

Lint nameClippy detectableRustc detectableLint GroupDefault level
tabs_in_doc_comments yesnoStylewarn
s

编码实践

常量

G.CNS.01 对于科学计算中涉及浮点数近似值的常量宜使用预定义常量

G.CNS.02 不应断言常量布尔类型

G.CNS.03 不应将内部可变性容器声明为常量

G.CNS.04 Shouldn't add explicit 'static lifetime in constant declaration

G.CNS.05 Should use const fn as much as possible

静态变量

G.STV.01 Shouldn't use static mut as global variable directly

Local Const

P.VAR.01 一般情况下避免先声明可变变量再赋值

P.VAR.02 利用变量遮蔽功能保证变量安全使用

G.VAR.01 以解构元组方式定义超过四个变量时不应使用太多无意义变量名

G.VAR.02 不应使用非 ASCII 字符作为标识符

G.VAR.03 变量遮蔽功能应当合理使用

G.VAR.04 避免因局部变量过大而导致的大量栈分配

数据类型

P.TYP.01 必要时,应使类型可以表达更明确的语义,而不是只是直接使用原生类型

G.TYP.01 类型转换尽可能使用安全的转换函数代替 as

G.TYP.02 数字字面量在使用的时候应该明确标注类型

G.TYP.03 不要用数字类型边界值判断能否安全转换,而应使用 try_from 方法

布尔

G.TYP.BOL.01 不应将布尔值和布尔字面量进行比较

G.TYP.BOL.02 如果 match 匹配表达式为布尔类型,宜使用 if 表达式来代替

G.TYP.BOL.03 不应将数字类型转换为布尔值

G.TYP.BOL.04 禁止在if表达式条件中使用块结构

G.TYP.BOL.05 非必要时,布尔运算应使用逻辑运算符( &&/||)而非位运算符 (&/|)

G.TYP.BOL.06 不应使用数字代替布尔值

字符

G.TYP.CHR.01 不应将字符字面量强制转换为 u8

G.TYP.CHR.02 字符串方法中如果需要单个字符的值作为参数,宜使用字符而非字符串

G.TYP.CHR.03 需要将整数转换为字符时,应使用安全转换函数,而非 transmute

整数

G.TYP.INT.01 在用整数计算的时候需要考虑整数溢出、回绕和截断的风险

G.TYP.INT.02 避免在有符号整数和无符号整数之间进行强制转换

G.TYP.INT.03 对负数取模计算的时候不应使用 %

浮点数

G.TYP.FLT.01 使用浮点数字面量时,要警惕是否存在被Rust编译器截断的风险

G.TYP.FLT.02 从任何数字类型转换为浮点类型时注意避免损失精度

G.TYP.FLT.03 对精度高要求的场景下,使用浮点数进行运算和比较时需要注意精度损失

G.TYP.FLT.04 宜使用Rust内置方法处理浮点数计算

G.TYP.FLT.05 禁止在浮点数和整数相互转换时使用 transmute

切片

P.TYP.SLC.01 宜使用切片迭代器来代替手工索引

P.TYP.SLC.02 宜使用切片模式来提升代码的可读性

元组

G.TYP.TUP.01 使用元组时,其元素不宜超过3个

固定长度数组

G.TYP.ARR.01 创建大全局数组时宜使用静态变量而非常量

G.TYP.ARR.02 使用数组索引时禁止越界访问

G.TYP.ARR.03 当数组元素为原生数据类型(Primitive),排序时优先选用非稳定排序

动态数组

P.TYP.VEC.01 非必要时不宜使用动态数组

P.TYP.VEC.02 创建动态数组时,宜预先分配足够容量,避免后续操作中产生多次分配

G.TYP.VEC.01 禁止访问未初始化的数组

结构体

P.TYP.SCT.01 为结构体实现构造性方法时,避免构造后再初始化的情况

P.TYP.SCT.02 结构体实例需要默认实现时,宜使用Default特质

G.TYP.SCT.01 对外导出的公开的 Struct,宜添加#[non_exhaustive]属性

G.TYP.SCT.02 当结构体中有超过三个布尔类型的字段,宜将其独立为新的枚举类

G.TYP.SCT.03 宜使用结构体功能更新语法来提升代码可读性

枚举体

G.TYP.ENM.01 合理使用map和and_then方法

G.TYP.ENM.02 不应自行创建空枚举

G.TYP.ENM.03 在使用类似 C 语言的枚举写法且使用repr(isize/usize) 布局时注意 32位架构上截断的问题

G.TYP.ENM.04 不宜在use语句中引入Enum的全部变体(variants)

G.TYP.ENM.05 对外导出的公开Enum,宜添加#[non_exhaustive]属性

G.TYP.ENM.06 Enum内变体的大小差异不宜过大

表达式

G.EXP.01 当需要对表达式求值后重新赋值时,宜使用复合赋值模式

G.EXP.02 不宜在比较中使用不兼容的位掩码

G.EXP.03 不应利用数组表达式的边界检查来 Panic,而应使用断言

G.EXP.04 自增或自减运算使用+=或-=

G.EXP.05 使用括号来清楚表示表达式的计算顺序

G.EXP.06 避免在比较中添加无用的掩码操作

控制流程

P.CTF.01 避免滥用迭代器

P.CTF.02 优先使用模式匹配而非判断后再取值

G.CTF.01 当需要通过多个if判断来比较大小来区分不同情况时,优先使用match和cmp来代替if表达式

G.CTF.02 if条件表达式分支中如果包含了else if分支也应该包含else分支

G.CTF.03 如果要通过 if 条件表达式来判断是否 Panic,请优先使用断言

G.CTF.04 在 Match 分支的 Guard 语句中不要使用带有副作用的条件表达式

字符串

P.STR.01 处理字符串元素时优先按字节处理而非字符

P.STR.02 创建字符串时,宜预先分配大约足够的容量来避免后续操作中产生多次分配

P.STR.03 在使用内建字符串处理函数或方法的时候,应注意避免隐藏的嵌套迭代或多次迭代

P.STR.04 只在合适的场景下,使用正则表达式第三方库regex

P.STR.05 在拼接字符串时,优先使用format!

G.STR.01 在实现Display特质时不应调用to_string()方法

G.STR.02 在追加字符串时使用push_str方法

G.STR.03 将只包含 ASCII字符的字符串字面量转为字节序列可以直接使用b"str" 语法代替调用as_bytes方法

G.STR.04 需要辨别字符串的字符开头或结尾字符时,不应按字符迭代比较

G.STR.05 对字符串按指定位置进行切片的时候需要小心破坏其 UTF-8 编码

集合容器

P.CLT.01 创建HashMap、VecDeque时,可以预先分配大约足够的容量来避免后续操作中产生多次分配

G.CLT.01 非必要情况下,不要使用LinkedList,而用Vec或VecDeque代替

Function Design

P.FUD.01 Should rebind variables which pass into closure

[Description]

By default, closure captures environment variables with borrow, or it is also allowed to pass environment variables into closure with move keyword.

It is more readable to rebind and regroup these variables which pass into closure.

[Bad Case]


#![allow(unused)]
fn main() {
use std::rc::Rc;
let num1 = Rc::new(1);
let num2 = Rc::new(2);
let num3 = Rc::new(3);
let closure = {
    // ownership of `num1` has been moved
    let num2 = num2.clone();
    let num3 = num3.as_ref();
    move || {
        *num1 + *num2+ *num3; // Not Good.
    }
};
}

[Good Case]


#![allow(unused)]
fn main() {
use std::rc::Rc;

let num1 = Rc::new(1);
let num2 = Rc::new(2);
let num3 = Rc::new(3);

// Good: rebind variables seperately, which would pass into closure.
let num2_cloned = num2.clone();
let num3_borrowed = num3.as_ref();
let closure = move || {
    *num1 + *num2_cloned + *num3_borrowed; // Good.
};
}

P.FUD.02 Don't Use return for return value

[Description]

In such that function would automatically return the value of last expression in Rust, it is not required to use return explicitly.

Only when it is necessary to return a value in procedure of function, should use return explicitly.

[Bad Case]


#![allow(unused)]
fn main() {
fn foo(x: usize) -> usize {
    if x < 42 {
        return x;
    }
    return x + 1; // Not Good
}
}

[Good Case]


#![allow(unused)]
fn main() {
fn foo(x: usize) -> usize {
    if x < 42 {
        return x;
    }
    x + 1 // Good
}
}

G.FUD.01 Less than 5 function parameters

[Level] Advice

[Description]

In order to improve readability of code, it's better to set less than 5 function's parameters.

[Bad Case]


#![allow(unused)]
fn main() {
struct Color;
// Not Good
fn foo(x: f32, y: f32, name: &str, c: Color, w: u32, h: u32, a: u32, b: u32) {
    // ..
}
}

[Good Case]

Refine function's parameters as possible.

struct Color;
// Good: use const generic here to receive multiple u32 typed parameters afterward.
// use tuple to refine 2~3 parameters as one.
fn foo<T, const N: usize>(x: (f32, f32), name: &str, c: Color, last: [T;N]) {
    ;
}

fn main() {
    let arr = [1u32, 2u32];
    foo((1.0f32, 2.0f32), "hello", Color, arr);
    let arr = [1.0f32, 2.0f32, 3.0f32];
    foo((1.0f32, 2.0f32), "hello", Color, arr);
}

[Lint Check]

lint nameClippy checkRustc CheckLint Grouplevel
too_many_argumentsyesnocomplexitywarn

This lint corresponds to the following configuration of clippy.toml:

# less than 5 parameters
too-many-arguments-threshold=5

G.FUD.02 Should input as reference if parameter derives Copy and its value inputed by-value is a big number

[Level] Advice

[Description]

By-value inputed parameters may emerge unnecessary memcpy, which could kill performance.

[Bad Case]


#![allow(unused)]
#![warn(clippy::large_types_passed_by_value)]

fn main() {
#[derive(Clone, Copy)]
struct TooLarge([u8; 2048]);

// Not Good
fn foo(v: TooLarge) {}
}

[Good Case]


#![allow(unused)]
#![warn(clippy::large_types_passed_by_value)]

fn main() {
#[derive(Clone, Copy)]
struct TooLarge([u8; 2048]);

// Good
fn foo(v: &TooLarge) {}
}

[Lint Check]

lint nameClippy checkRustc checkLint GroupLevel
large_types_passed_by_valueyesnopedanicallow

This lint corresponds to the following configuration of clippy.toml

# if function is an exported api, then the lint would not be triggered, which aims to prevent devastated change for api. the default is true.
avoid-breaking-exported-api=true

G.FUD.03 Should refactor into customized struct or enum if there are too many bool type's function parameters

[Level] Advice

[Description]

Too many bool parameters is bad for memory and prone to issue errors. As to use typeinference and borrowchecker effectively, we should refactor into cutomized struct and enum in this case.

[Bad Case]


#![allow(unused)]
#![warn(clippy::fn_params_excessive_bools)]

fn main() {
// Not Good
fn f(is_rould: bool, is_hot: bool) { ... }
}

[Good Case]


#![allow(unused)]
#![warn(clippy::fn_params_excessive_bools)]

fn main() {
enum Shap {
    Round,
    Spiky,
}

enum Temperature {
    Hot,
    IceCold,
}

// Good
fn f(shape: Shape, temperature: Temperature) { ... }
}

[Lint Check]

lint nameClippy checkRustc checkLint GroupLevel
fn_params_excessive_boolsyesnopedanicallow

This lint corresponds to the following configuration of clippy.toml

# In order to limit maximum number of bool type's parameters, the default is 3.
max-fn-params-bools=3

G.FUD.04 Should input by-value but by-ref if parameter derived Copy, and its value is a small enough number

[Level] Advice

[Description]

Generally, it is avoided to input by-ref when parameter's value is a small enough number and derived Copy. That's because as for such small values, from the perspective of performance, by-value is as fast as by-ref, and it could also make code more readable. It is also recommended to input by-value for some tiny struct, but should notice [Exception] cases.

[Bad Case]


#![allow(unused)]
#![warn(clippy::trivially_copy_pass_by_ref)]

fn main() {
// Not Good
fn foo(v: &u32) { ... }
}

[Good Case]


#![allow(unused)]
#![warn(clippy::trivially_copy_pass_by_ref)]

fn main() {
// Good
fn foo(v: u32) { ... }
}

[Exception]

Should notice following cases, lint checker would report trivial warnings because of its conservativeness.

#[derive(Clone, Copy)]
struct RawPoint {
    pub x: u8,
}

#[derive(Clone, Copy)]
struct Point {
    pub raw: RawPoint,
}

impl Point {
    pub fn raw(&self) -> *const RawPoint {
        &self.raw
    }

    // if you conform lint checker, change above `raw(&self)`'s parameter `&self` into `self`(delete ref), it is `raw_linted(self)`. That also works well in unoptimized cases(just like `cargo build`, the debug mode), but doesn't work in optimized cases(i.e. `cargo build --release`, the release mode) because of following reasons.
    pub fn raw_linted(self) -> *const RawPoint {
        &self.raw
    }
}

fn main() {
    let p = Point { raw: RawPoint { x: 10 } };

    // This passes
    assert_eq!(p.raw(), p.raw());

    // This fails
    // Actually, in optimized cases, the activity of function has been changed if not using `self` as shared ref.
    // Because struct Point derived Copy trait, everytime we call raw_linted() method, its instance would be copied, and return a different pointer.
    assert_eq!(p.raw_linted(), p.raw_linted());
}

[Lint Check]

lint nameClippy checkRustc checkLint GroupLevel
trivially_copy_pass_by_refyesnopedanicallow

This lint corresponds to the following configuration of clippy.toml

# if it is an exported API, the lint checker would not be triggered, which avoid unexpected modifications.
avoid-breaking-exported-api=true

# consider the maximum size (bytes) of by-value pass with Copy. The default is None.
trivial-copy-size-limit=None

Tips: this lint would not consider the cases using pointer, just like example in [Exception] part.

Reference of [Exception] example: rust-clippy/issues/5953.

G.FUD.05 Don't always specify #[inline(always)] for function

[Level] Advice

[Description]

inline could imporve performance, but also increase compilation time and size. You have to make a tradeoff among Performance, Compilation time and Compilation size in Rust, and it is recommended to use inline on demand.

[Bad Case]


#![allow(unused)]
#![warn(clippy::inline_always)]

fn main() {
// Not Good
#[inline(always)]
fn not_quite_hot_code(..) { ... }
}

[Exception]

Use inline on demand. i.e. it is obvious that one function is called frequently, and you could use inline for it to improve performance.


#![allow(unused)]
fn main() {
// Good: that function is often called for recycle buffer memory, so use `inline` maximize its performance.
pub fn buf_recycle(buf_id: usize) {
    // ...
}
}

[Lint Check]

lint nameClippy checkRustc checkLint GroupLevel
inline_alwaysyesnopedanicallow

G.FUD.06 Parameters Should be compatible with multiple types

[Level] Advice

[Description]

It could enable parameters to be compatible with multiple types, which is benificial for the scalability of code.

[Bad Case]

// Not Good
fn  three_vowels(word: &String) -> bool {
    let mut vowel_count = 0;
    for c in word.chars() {
        match c {
            'a' | 'e' | 'i' | 'o' | 'u' => {
                vowel_count += 1;
                if vowel_count >= 3 {
                    return true
                }
            }
            _ => voweled_count = 0
        }
    }
    false
}

fn main() {
    let sentence_string = "Once upon a time, there was a friendly curious crab named Ferris".to_string();
    for word in sentence_string.split(' ') {
        if three_vowels(word.to_string()) {
            println!("{} has three consecutive vowels!", word);
        }
    }
}

[Good Case]

// Good: Three types could be accepted as parameter here: &String / &str / &'static str.
fn three_vowels(word: &str) -> bool {
    let mut vowel_count = 0;
    for c in word.chars() {
        match c {
            'a' | 'e' | 'i' | 'o' | 'u' => {
                vowel_count += 1;
                if vowel_count >= 3 {
                    return true
                }
            }
            _ => vowel_count = 0
        }
    }
    false
}

fn main() {
    let sentence_string = 
        "Once upon a time, there was a friendly curious crab named Ferris".to_string();
    for word in sentence_string.split(' ') {
        if three_vowels(word) {
            println!("{} has three consecutive vowels!", word);
        }
    }
}

泛型

P.GEN.01 用泛型来抽象公共语义

P.GEN.02 不要随便使用 impl Trait 语法替代泛型限定

P.GEN.03 不要使用太多泛型参数和 trait 限定,否则会增长编译时间

P.GEN.04 为泛型类型实现方法时,impl 中声明的泛型类型参数一定要被用到

P.GEN.05 定义泛型函数时,如果该函数实现用到来自 trait 定义的相关行为,需要为泛型指定相关 trait 的限定

G.GEN.01 不要在泛型位置上使用内建类型

G.GEN.02 使用 Rust 标准库中某些方法,要注意避免使用其泛型默认实现,而应该使用具体类型的实现

特质

P.TRA.01 使用 trait 时要注意 trait 一致性规则

标准库内置 trait

P.TRA.BLN.01 在实现Borrow特质时,需要注意一致性

G.TRA.BLN.01 应该具体类型的 default() 方法代替 Default::default() 调用

G.TRA.BLN.02 不要为迭代器实现Copy 特质

G.TRA.BLN.03 能使用派生宏(Derive)自动实现Default特质就不要用手工实现

G.TRA.BLN.04 在使用#[derive(Hash)] 的时候,避免再手工实现 PartialEq

G.TRA.BLN.05 在使用#[derive(Ord)] 的时候,避免再手工实现 PartialOrd

G.TRA.BLN.06 不要对实现 Copy 或引用类型调用 std::mem::drop 和 std::mem::forgot

G.TRA.BLN.07 对实现 Copy 的可迭代类型来说,要通过迭代器拷贝其所有元素时,应该使用 copied方法,而非cloned

G.TRA.BLN.08 实现 From 而不是 Into

G.TRA.BLN.09 一般情况下不要给 Copy 类型手工实现 Clone

G.TRA.BLN.10 不要随便使用Deref特质来模拟继承

trait 对象

P.TRA.OBJ.01 根据场景合理选择使用trait对象或泛型静态分发

P.TRA.OBJ.02 除非必要,避免自定义虚表

错误处理

P.ERR.01 当传入函数的参数值因为超出某种限制可能会导致函数调用失败,应该使用断言

P.ERR.02 在确定 Option 和 Result<T, E>类型的值不可能是 None 或 Err 时,请用 expect 代替 unwrap()

G.ERR.01 在处理 Option 和 Result<T, E> 类型时,不要随便使用 unwrap

G.ERR.02 不要滥用 expect,请考虑用 unwrap_or_ 系列方法代替

内存管理

生命周期

P.MEM.LFT.01 生命周期参数命名尽量有意义且简洁

P.MEM.LFT.02 通常需要显式地标注生命周期,而非利用编译器推断

智能指针

P.MEM.SPT.01 使用 RefCell 时宜使用 try_borrow/try_borrow_mut 方法

Box 类型

G.MEM.BOX.01 一般情况下,不应直接对 Box 进行借用

G.MEM.BOX.02 一般情况下,不应直接对已经在堆上分配内存的类型进行 Box 装箱

G.MEM.BOX.03 一般情况下,不应直接对栈分配类型进行 Box 装箱

Drop 析构

G.MEM.DRP.01 要注意防范内存泄漏

模块

P.MOD.01 合理控制对外接口和模块之间的可见性

P.MOD.02 将模块的测试移动到单独的文件,有助于增加编译速度

G.MOD.01 使用导入模块中的类型或函数,在某些情况下需要带模块名前缀

G.MOD.02 如果是作为库供别人使用,在 lib.rs中重新导出对外类型、函数和 trait 等

G.MOD.03 导入模块不要随便使用 通配符*

G.MOD.04 一个项目中应该避免使用不同的模块布局风格

G.MOD.05 不要在私有模块中设置其内部类型或函数方法为 pub(crate)

Cargo package management

P.CAR.01 应该尽量把项目划分为合理的 crate 组合

P.CAR.02 不要滥用 Features

P.CAR.03 使用 cargo features 来代替 --cfg 条件编译参数

P.CAR.04 如果可能的话,使用 cfg! 来代替 #[cfg]

G.CAR.01 当项目是可执行程序而非库时,建议使用 src/main.rs 和 src/lib.rs 模式

G.CAR.02 Crate 的 Cargo.toml 中应该包含必要的元信息

G.CAR.03 Feature 命名应该避免否定式或多余的前后缀

G.CAR.04 Cargo.toml 中依赖包版本不应使用通配符

P.MAC.01 不要轻易使用宏

P.MAC.02 实现宏语法的时候,应该尽量贴近 Rust 语法

G.MAC.01 dbg!() 宏只应该用于调试代码

G.MAC.02 使用宏时应该考虑宏展开会让编译文件膨胀的影响

声明宏

P.MAC.DCL.01 不要将声明宏内的变量作为外部变量使用

P.MAC.DCL.02 在编写多个宏规则时,应该先从匹配粒度最小的开始写

P.MAC.DCL.03 不要在片段分类符跟随它不匹配的符号

P.MAC.DCL.04 匹配规则要精准,不要模糊不清

P.MAC.DCL.05 使用宏替换(substitution)元变量的时候要注意选择合适的片段分类符

P.MAC.DCL.06 当宏需要接收 self 时需要注意

P.MAC.DCL.07 确保在宏定义之后再去调用宏

P.MAC.DCL.08 同一个 crate 内定义的宏相互调用时,需要注意卫生性

过程宏

P.MAC.PRO.01 不要使用过程宏来规避静态分析检查

P.MAC.PRO.02 实现过程宏时要对关键特性增加测试

P.MAC.PRO.03 保证过程宏的卫生性

P.MAC.PRO.04 给出正确的错误位置

P.MAC.PRO.05 代码生成要按情况选择使用过程宏还是 build.rs

P.MAC.PRO.06 build.rs 生成的代码要保证没有任何警告

多线程

锁同步

P.MTH.LCK.01 多线程下要注意识别锁争用的情况,避免死锁

G.MTH.LCK.01 对布尔或引用并发访问应该使用原子类型而非互斥锁

G.MTH.LCK.02 宜使用 Arc / Arc<[T]> 来代替 Arc / Arc

G.MTH.LCK.03 尽量避免直接使用标准库 std::sync 模块中的同步原语,替换为 parking_lot

G.MTH.LCK.04 尽量避免直接使用标准库 std::sync::mpsc 模块中的 channel,替换为 crossbeam

无锁

P.MTH.LKF.01 除非必要,否则建议使用同步锁

P.MTH.LKF.02 使用无锁编程时,需要合理选择内存顺序

Async

P.ASY.01 异步编程并不适合所有场景,计算密集型场景应该考虑同步编程

G.ASY.01 在 async 块或函数中调用 async 函数或闭包请不要忘记添加.await

G.ASY.02 在跨 await 调用中,需要对其持有的同步互斥锁进行处理

G.ASY.03 在跨 await 调用中,需要对其持有 RefCell 的引用进行处理

G.ASY.04 避免定义不必要的异步函数

G.ASY.05 避免在异步处理过程中包含阻塞操作

Unsafe Rust

P.UNS.01 不要为了逃避编译器安全检查而滥用 Unsafe Rust

P.UNS.02 不要为了提升性能而盲目使用 Unsafe Rust

G.UNS.01 不宜为带有 unsafe 命名的类型或方法创建别名

安全抽象

P.UNS.SAS.01 代码中要注意是否会因为 Panic 发生而导致内存安全问题

P.UNS.SAS.02 Unsafe 代码编写者有义务检查代码是否满足安全不变式

P.UNS.SAS.03 不要随便在公开的 API 中暴露未初始化内存

P.UNS.SAS.04 避免因为 Panic Safety 而导致双重释放

P.UNS.SAS.05 手动实现 auto trait 时要充分考虑其安全性

P.UNS.SAS.06 不要随便在公开的 API 中暴露裸指针

P.UNS.SAS.07 在抽象安全方法的同时,也建议为性能考虑而增加相应的 Unsafe 方法

P.UNS.SAS.08 函数参数是不可变借用的时候,返回值不应该是可变借用

P.UNS.SAS.09 在任何 Unsafe 块之前都应该加 SAFETY 注释

G.UNS.SAS.01 在公开的 unsafe 函数的文档中必须增加 Safety 注释

G.UNS.SAS.02 在 Unafe 函数中应使用 assert! 而非 debug_assert! 去校验边界条件

裸指针操作

P.UNS.PTR.01 不要将裸指针在多线程间共享

P.UNS.PTR.02 建议使用 NonNull 来替代 *mut T

P.UNS.PTR.03 使用指针类型构造泛型结构体时,需要使用 PhantomData 来指定 T上的协变和所有权

G.UNS.PTR.01 当指针类型被强转为和当前内存对齐不一致的指针类型时,禁止对其解引用

G.UNS.PTR.02 禁止将不可变指针手工转换为可变指针

G.UNS.PTR.03 尽量使用 pointer::cast 来代替 使用 as 强转指针

联合体

P.UNS.UNI.01 除了与 C 交互,尽量不要使用 Union

P.UNS.UNI.02 不要把联合体的不同变体用在不同生命周期内

内存

P.UNS.MEM.01 要注意选择合适的结构体、元组、枚举的数据布局

P.UNS.MEM.02 不能修改其它进程或动态库的内存变量

P.UNS.MEM.03 不能让 String/Vec 自动 Drop 其它进程或动态库的内存数据

P.UNS.MEM.04 尽量用可重入(reentrant)版本的 C-API 或系统调用

P.UNS.MEM.05 如果需要使用位域,推荐使用第三方库

G.UNS.MEM.01 使用 MaybeUninit 来处理未初始化的内存

FFi

P.UNS.FFI.01 避免从公开的 Rust API 直接传字符串到 C 中

P.UNS.FFI.02 在使用标准库 std::ffi 模块提供的类型时需要仔细查看其文档

P.UNS.FFI.03 当使用来自 C 的指针时,如果该指针需要管理内存,则需要为包装该指针的 Rust 类型实现 Drop 特质

P.UNS.FFI.04 如果一个函数正在跨越 FFi 边界,那么需要处理 Panic

P.UNS.FFI.05 建议使用诸如标准库或 libc crate 所提供的可移植类型别名,而不是特定平台的类型

P.UNS.FFI.06 Rust 和 C 之间传递字符或字符串时需要注意字符串要符合 C-ABI 以及 字符串的编码

P.UNS.FFI.07 不要为任何传出外部的类型实现 Drop

P.UNS.FFI.08 FFi 中要进行合理的错误处理

P.UNS.FFI.09 当 Rust 调用外部 C 函数时,如果可以确认安全,可以通过引用来代替裸指针

P.UNS.FFI.10 当 Rust 函数导出外部函数时,必须从设计上保证被跨线程调用的安全性

P.UNS.FFI.11 如需引用指定为 #[repr(packed)] 内存布局的结构体成员字段要注意合理规避未定义行为

P.UNS.FFI.12 当依赖 C 端传入参数时,需要在文档注释中不变性声明,根据不同的调用场景选择合适的安全抽象方式

P.UNS.FFI.13 自定义数据类型要保证一致的数据布局

P.UNS.FFI.14 在 FFi 中使用的类型应该拥有稳定布局

P.UNS.FFI.15 从外部传入的不健壮类型的外部值要进行检查

I/O

P.UNS.FIO.01 在使用原始句柄的时候,要注意 I/O 安全性

Unsafe 代码术语指南

no-std

P.EMB.01 no-std 下必须定义一个Panic行为以确保安全

P.EMB.02 no-std 下要确保程序中的类型有正确的内存布局

I/O

P.FIO.01 使用 read_to_end/read_to_string方法时注意文件的大小能否一次性读入内存中

G.FIO.01 文件读取建议使用 BufReader/BufWriter 来代替 Reader/Write

信息安全

P.SEC.01 使用第三方库的时候要确保可信的依赖,小心供应链攻击

G.SEC.01 代码中不要出现非法 Unicode 字符,也要防范非法 Unicode 字符

其他

G.OTH.01 对于某些场景下不建议使用的方法可以通过配置 clippy.toml 来拒绝

G.OTH.01 使用标准库中对应的方法计算秒级、毫秒级、微秒级的时间

附录

A.开发环境

B.测试

单元测试

基准测试

模糊测试

C.术语解释

D.模板

rustfmt 模板

clippy 模板

deny 模板

E.工具链

rustfmt

noisy-clippy

cargo-udeps

F.Cheat Sheet

浮点数

G.优化指南

H.编译参数说明

I.最佳实践

初学者常见问题Q&A

Rust 编程技巧

J.贡献说明