Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/safe-guides/coding_practice/async-await.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 异步编程

`async/.await` 是 Rust 语言用于编写像同步代码一样的异步函数的内置工具。`async` 将一个代码块转化为一个实现了名为 `Future` 的特质 (trait)
的状态机。虽然在同步方法中调用阻塞函数会阻塞整个线程,但阻塞的 `Futures` 将让出线程控制权,允许其他 `Futures` 运行。
`async / await` 是 Rust 语言用于编写像同步代码一样的异步函数的内置工具。`async` 将一个代码块转化为一个实现了名为 `Future` 的特质 (trait)
的状态机。虽然在同步方法中调用阻塞函数会阻塞整个线程,但阻塞的 `Future` 将让出线程控制权,允许其他 `Future` 运行。

Rust 异步编程需要依赖于异步运行时,生产环境中比较推荐的开源异步运行时是 [Tokio](https://github.com/tokio-rs/tokio)。

Expand Down
5 changes: 2 additions & 3 deletions src/safe-guides/coding_practice/async-await/G.ASY.01.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,15 @@ fn bar() {

**【例外】**

```rust
// https://docs.rs/crate/fishrock_lambda_runtime/0.3.0-patched.1/source/src/lib.rs#:~:text=clippy%3a%3aasync_yields_async
用例来源:[fishrock_lambda_runtime](https://docs.rs/crate/fishrock_lambda_runtime/0.3.0-patched.1/source/src/lib.rs#:~:text=clippy%3a%3aasync_yields_async)

```rust
#[allow(clippy::async_yields_async)]
let task = tokio::spawn(async move { handler.call(body, ctx) });

let req = match task.await {
// ...
}

```

**【Lint 检测】**
Expand Down
51 changes: 26 additions & 25 deletions src/safe-guides/coding_practice/async-await/G.ASY.02.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
## G.ASY.02 在 跨 `await` 调用中持有同步互斥锁需要进行处理
## G.ASY.02 在跨 `await` 调用中,需要对其持有的同步互斥锁进行处理

**【级别】** 建议

**【描述】**

同步互斥锁本来就不是为异步上下文跨 `await` 调用而设计的,在这种场景中使用同步互斥锁容易造成死锁。当同步互斥锁被跨 await 时,有可能很长时间都不会返回这个调用点,在其他任务中再次用到这个互斥锁的时候,容易造成死锁。
同步互斥锁本来就不是为异步上下文跨 `await` 调用而设计的,在这种场景中使用同步互斥锁容易造成死锁。当同步互斥锁被跨 `await` 时,有可能很长时间都不会返回这个调用点,在其他任务中再次用到这个互斥锁的时候,容易造成死锁。

这里有三种解决方案:

Expand All @@ -29,11 +29,11 @@ async fn foo(x: &Mutex<u32>) {
use std::sync::Mutex;
// 使用同步互斥锁
async fn foo(x: &Mutex<u32>) {
{
let guard = x.lock().unwrap();
*guard += 1;
}
bar.await;
{
let guard = x.lock().unwrap();
*guard += 1;
}
bar.await;
}

// 使用异步互斥锁
Expand Down Expand Up @@ -69,27 +69,28 @@ async fn main() {

**【例外】**

用例来源:[kludgine](https://github.com/khonsulabs/kludgine/blob/dafc1b5bab10702265cdd1d8ab210ce882d0f998/app/src/runtime/smol.rs#L31)

```rust
// FROM: https://github.com/khonsulabs/kludgine/blob/main/app/src/runtime/smol.rs#L31
// Launch a thread pool
std::thread::spawn(|| {
let (signal, shutdown) = flume::unbounded::<()>();

easy_parallel::Parallel::new()
// Run four executor threads.
.each(0..4, |_| {
#[allow(clippy::await_holding_lock)] // 这里是 读写锁,不是互斥锁
futures::executor::block_on(async {
let guard = GLOBAL_THREAD_POOL.read(); // 获取读写锁的读锁,不会出现锁争用情况,所以是线程安全的
let executor = guard.as_ref().unwrap();
executor.run(shutdown.recv_async()).await
})
// Launch a thread pool
std::thread::spawn(|| {
let (signal, shutdown) = flume::unbounded::<()>();

easy_parallel::Parallel::new()
// Run four executor threads.
.each(0..4, |_| {
#[allow(clippy::await_holding_lock)] // 这里是 读写锁,不是互斥锁
futures::executor::block_on(async {
let guard = GLOBAL_THREAD_POOL.read(); // 获取读写锁的读锁,不会出现锁争用情况,所以是线程安全的
let executor = guard.as_ref().unwrap();
executor.run(shutdown.recv_async()).await
})
// Run the main future on the current thread.
.finish(|| {});
})
// Run the main future on the current thread.
.finish(|| {});

drop(signal);
});
drop(signal);
});
```

**【Lint 检测】**
Expand Down
21 changes: 13 additions & 8 deletions src/safe-guides/coding_practice/async-await/G.ASY.03.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
## G.ASY.03 在 跨 `await` 调用持有 `RefCell` 的引用需要进行处理
## G.ASY.03 在跨 `await` 调用中,需要对其持有 `RefCell` 的引用进行处理

**【级别:建议】**

**【描述】**

跟不要在异步上下文中跨 `await` 使用 同步互斥锁类似,使用 `RefCell` 的独占(可变)借用会导致 Panic。因为 `RefCell` 是运行时检查独占的可变访问,如果 跨 `await` 持有一个可变引用则可能会因为共享的可变引用而引起 Panic。
与[上条规则](./G.ASY.02.md)类似,使用 `RefCell` 的独占(可变)借用会导致 Panic。因为 `RefCell` 是运行时检查独占的可变访问,如果跨 `await` 持有一个可变引用则可能会因为共享的可变引用而引起 Panic。

这种共享可变在编译期是无法被检查出来的。

Expand All @@ -27,8 +27,8 @@ use std::cell::RefCell;

async fn foo(x: &RefCell<u32>) {
{
let mut y = x.borrow_mut();
*y += 1;
let mut y = x.borrow_mut();
*y += 1;
}
bar.await;
}
Expand All @@ -38,15 +38,20 @@ async fn foo(x: &RefCell<u32>) {

跨 `await` 持有 `RefCell` 的可变借用,但是当前场景确信永远不会 Panic,则可以使用。

用例来源:[wasm-streams](https://github.com/MattiasBuelens/wasm-streams/blob/dff05d77513cc1d590c21cd251a63b43cf520fed/src/readable/into_underlying_byte_source.rs#L65)

```rust
// From : https://github.com/MattiasBuelens/wasm-streams/blob/master/src/readable/into_underlying_byte_source.rs#L65
let fut = async move {
pub fn pull(&mut self, controller: sys::ReadableByteStreamController) -> Promise {
let inner = self.inner.clone();
let fut = async move {
// This mutable borrow can never panic, since the ReadableStream always queues
// each operation on the underlying source.
// 这个可变借用永远不会恐慌,因为 ReadableStream 对底层源的每个操作总是有序的。
// 这个可变借用永远不会恐慌,因为 ReadableStream 对底层源的每个操作总是有序的。
let mut inner = inner.try_borrow_mut().unwrap_throw();
inner.pull(controller).await
};
};
// ...
}
```

**【Lint 检测】**
Expand Down
2 changes: 1 addition & 1 deletion src/safe-guides/coding_practice/async-await/G.ASY.05.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ async fn read_file() -> std::io::Result<()> {
| --------- | ------------- | ------------ | ---------- | ---------- |
| _ | no | no | _ | yes |

【定制化参考】
**【定制化参考】**
这条规则如果需要定制Lint,则可以扫描异步过程,找到黑名单定义的阻塞操作调用,进行告警。
2 changes: 1 addition & 1 deletion src/safe-guides/coding_practice/cargo/G.CAR.01.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ bin/
1. 便于单元测试。
2. 这样拆分有利于面向接口思考,让代码架构和逻辑更加清晰。

如果你编写的可执行程序比较复杂时,在 `main.rs` 里需要依赖太多东西时,那就需要创建 Workspace把 `main.rs` 在独立为一个 crate,而在这个 crate 内也没有必要再拆分为 `main` 和 `lib` 了。
若编写的可执行程序比较复杂,在 `main.rs` 里需要依赖太多东西时,那就需要创建 Workspace 把 `main.rs` 独立为一个 crate,而在这个 crate 内也没有必要再拆分为 `main` 和 `lib` 了。

**【Lint 检测】**

Expand Down
3 changes: 2 additions & 1 deletion src/safe-guides/coding_practice/cargo/G.CAR.02.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

**【描述】**

在 Cargo.toml 中应该包含必要的元信息,以便使用者知道它的作用。并且后续上传到 crates.io 上,这些信息也是必须的。
在 Cargo.toml 中应该包含必要的元信息,以便使用者知道它的作用。
此外,若要将 `crate` 发布到 crates.io 上的话,这些信息也是必须的。可参考 The Cargo Book 中的[相关介绍](https://doc.rust-lang.org/cargo/reference/manifest.html)。

**【反例】**

Expand Down
11 changes: 5 additions & 6 deletions src/safe-guides/coding_practice/cargo/G.CAR.03.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@

**【级别】** 建议

### 【描述】
**【描述】**

Feature 命名应该避免出现 `no-` 或 `not-` 之类的否定前缀,或诸如 `use-`,`with-` 前缀或 `-support`后缀。Feature 的目的是正向的,可选的特性,使用否定式命名和它的目的背道而驰。

**【反例】**

```toml
# The `Cargo.toml` with negative feature names
[features]
default = ["with-def", "ghi-support"]
no-abc = []
with-def = [] // redundant
ghi-support = [] // redundant
default = ["no-abc", "with-def", "ghi-support"]
no-abc = [] # 命名否定式
with-def = [] # 多余前缀
ghi-support = [] # 多余后缀
```

**【正例】**
Expand Down
6 changes: 3 additions & 3 deletions src/safe-guides/coding_practice/cargo/P.CAR.01.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
## P.CAR.01 应该尽量把项目划分为合理的 crate 组合
## P.CAR.01 应该尽量把项目划分为合理的 crate 组合

**【描述】**

将整个项目按一定逻辑划分为合理的 crate,在工程方面,有利于组件化。并且 crate 是 Rust 的编译单元,也有助于提升编译时间
将整个项目按一定逻辑划分为合理的 crate,在工程方面有利于组件化。并且 crate 是 Rust 的编译单元,也有助于提升编译速度

但需要注意,crate 之间的依赖关系应该是单向的,避免相互依赖的情况。

但 Rust 中 编译时间、性能、编译大小之间,在考虑优化的时候,也是需要权衡的
但 Rust 中 编译时间、性能、编译大小之间,在考虑优化的时候也是需要权衡的

内联是优化的关键,当编译单元越大,内联优化效果就越好。所以需要权衡 crate 划分的粒度。
6 changes: 3 additions & 3 deletions src/safe-guides/coding_practice/cargo/P.CAR.02.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
## P.CAR.02 不要滥用 `features`
## P.CAR.02 不要滥用 Features

**【描述】**

Rust 的 features 提供了方便的条件编译功能。从软件工程来说,features 应该是为了避免让用户依赖没必要依赖的功能而使用的。
Rust 的 features 提供了方便的条件编译功能。从软件工程来说,features 应该是为了避免让用户依赖没必要依赖的功能而使用的。

在使用 features 的时候,应该考虑到底是不是真的需要 features。

滥用features会带来额外的测试和静态检查的难度,需要保证不同features下的测试覆盖和静态检查情况
滥用 features 会带来额外的测试和静态检查的难度,需要保证不同 features 下的测试覆盖和静态检查情况
2 changes: 1 addition & 1 deletion src/safe-guides/coding_practice/cargo/P.CAR.03.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## P.CAR.03 使用 `cargo features` 来代替 `-cfg` 条件编译参数
## P.CAR.03 使用 `cargo features` 来代替 `--cfg` 条件编译参数

**【描述】**

Expand Down
2 changes: 1 addition & 1 deletion src/safe-guides/coding_practice/cargo/P.CAR.04.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

**【描述】**

`cfg!` 就像正常代码一样,会 check 全部函数逻辑,而 `#[cfg]` 是条件编译,会跳过一些 `Dead Code`
`cfg!` 就像正常代码一样,会检查全部函数逻辑,而 `#[cfg]` 是条件编译,会跳过一些 Dead Code。
17 changes: 9 additions & 8 deletions src/safe-guides/coding_practice/macros.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
# 宏

Rust 通过宏来支持元编程。其中宏有很多种,按实现方式可以分为两大类:声明宏(Declarative) 和 过程宏(Procedural)。
Rust 通过宏来支持元编程。其中宏有很多种,按实现方式可以分为两大类:声明宏(Declarative)和 过程宏(Procedural)。

按功能效果,过程宏又可以分为三类:

1. Bang 宏。类似于声明宏那样,像函数调用一样去使用的宏。
1. Function-like 宏。类似于声明宏那样,像函数调用一样去使用的宏。
2. Derive 宏。用于为数据类型自动生成一些 语法项(item),比如 trait 、结构体、方法等。
3. Attrubutes 宏。用于更加通用的代码生成功能。
3. Attribute 宏。用于更加通用的代码生成功能。

Rust 语言核心库和标准库,都内置了一些声明宏和过程宏,以方便开发者使用。
Rust 语言核心库和标准库都内置了一些声明宏和过程宏,以方便开发者使用。

内置的属性宏按功能大体又可以分为四类:
> 注:属性宏固定语法为 `#[attr]` 或 `#![attr]`,以下使用用例均已简化为 `attr` 的形式。即 `test`, `allow(c)` 代表其在 Rust 内的实现可分别表现为 `#[test]` 及 `#[allow(c)]`。

1. 测试属性。`#[test]` 属性宏用于将某个函数标记为单元测试函数。
1. 测试属性。`test` 属性宏用于将某个函数标记为单元测试函数。
2. 诊断([Diagnostic](https://doc.rust-lang.org/reference/attributes/diagnostics.html#diagnostic-attributes))属性。用于在编译过程中控制和生成诊断信息。包括:
1. `allow(c)`/ `warn(c)`/ `deny(c)`/ `forbid(c)` 等。
2. `#[must_use]` 。
3. [代码生成属性](https://doc.rust-lang.org/reference/attributes/codegen.html)。包括:`inline` / `cold` / `\#[target_feature]` 等。
1. `allow(c)` / `warn(c)` / `deny(c)` / `forbid(c)` 等。
2. `must_use` 。
3. [代码生成属性](https://doc.rust-lang.org/reference/attributes/codegen.html)。包括:`inline` / `cold` / `target_feature` 等。
4. [编译时限制属性](https://doc.rust-lang.org/reference/attributes/limits.html)。包括:`recursion_limit ` / `type_length_limit` 。
5. [类型系统属性](https://doc.rust-lang.org/reference/attributes/type_system.html)。包括:`non_exhaustive` 。

Expand Down
4 changes: 2 additions & 2 deletions src/safe-guides/coding_practice/macros/P.MAC.01.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**【描述】**

能使用宏写出强大和用户友好的宏API的人,重点不是因为他们对宏如何实现掌握的好,而是因为他们也掌握了宏之外关于 Rust 的一切。
能使用宏写出强大和用户友好的宏API的人,重点不仅是因为他们对宏如何实现掌握的好,而是因为他们同时也掌握了宏之外关于 Rust 的一切。

宏设计的重点在于宏生成什么样的代码,而不是宏如何生成代码。

Expand All @@ -14,4 +14,4 @@

**【参考】**

[Rust 社区顶级专家 Dtolnay 写的宏学习案例](https://github.com/dtolnay/case-studies)
[Rust 社区顶级专家 David Tolnay 写的宏学习案例](https://github.com/dtolnay/case-studies)
8 changes: 4 additions & 4 deletions src/safe-guides/coding_practice/macros/P.MAC.02.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@

**【描述】**

Rust 宏可以让开发者定义自己的DSL,但是,在使用宏的时候,要尽可能贴近Rust的语法。这样可以增强可读性,让其他开发者在使用宏的时候,可以猜测出它的生成的代码。
Rust 宏可以让开发者定义自己的DSL,但是在使用宏的时候,要尽可能贴近Rust的语法。这样可以增强可读性,让其他开发者在使用宏的时候,可以猜测出它的生成的代码。

**【反例】**

```rust
// ...over no keyword...
// 无关键词
bitflags! {
S: u32 { /* ... */ }
}

// ...or some ad-hoc word.
// 或使用一些自定义的特定用途关键词
bitflags! {
flags S: u32 { /* ... */ }
}

// or
//
bitflags! {
struct S: u32 {
const E = 0b010000, // 结尾应该是分号更符合 Rust 语法
Expand Down
4 changes: 2 additions & 2 deletions src/safe-guides/coding_practice/macros/decl.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# 声明宏

[声明宏](https://doc.rust-lang.org/reference/macros-by-example.html) 也被叫做 示例宏(macros by example),或者简单地叫做 宏。目前声明宏使用 ``macro_rules!``来定义。
[声明宏](https://doc.rust-lang.org/reference/macros-by-example.html) 也被叫做 示例宏(macros by example),或者简单地叫做 宏。目前声明宏使用 `macro_rules!`来定义。

声明宏的特点是,它只用作 代码替换,而无法进行计算。
声明宏的特点是:它只用作代码替换,而无法进行计算。

---
<!-- toc -->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## P.MAC.Decl.05 使用宏替换(substitution)元变量的时候要注意选择合适的片段分类符
## P.MAC.Decl.05 使用宏替换(substitution)元变量的时候要注意选择合适的片段分类符

**【描述】**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**【描述】**

`self` 在 Rust 中属于关键字,它会在代码运行时被替换为具体类型的实例。当它传递给 宏 时,它会被看做一个变量,而宏对于变量而言,是具备卫生性的。而且,声明宏的作用只是替换,而非计算,它并不能计算出 self 的具体类型。
`self` 在 Rust 中属于关键字,它会在代码运行时被替换为具体类型的实例。当它传递给 宏 时会被看做为一个变量,而宏对于变量而言是具备卫生性的。而且,声明宏的作用只是替换而非计算,它并不能计算出 self 的具体类型。

**【反例】**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**【描述】**

Rust 中类型或函数,你可以在定义前后都可以调用它,但是宏不一样。Rust 查找宏定义是按词法依赖顺序的,必须注意定义和调用的先后顺序。
Rust 中类型或函数在定义前后都可以调用,但是宏不一样。Rust 查找宏定义是按词法依赖顺序的,必须注意定义和调用的先后顺序。

**【反例】**

Expand Down
4 changes: 2 additions & 2 deletions src/safe-guides/coding_practice/macros/proc.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

syn/quote 不仅能用于过程宏,还广泛用于代码生成(*codegen*)、静态分析等用途,例如 tonic-build/prost 源码中也用到了 syn/quote 。

因此本过程宏规范不仅适用于过程宏,部分规范(例如 [P.MAC.Proc.06](safe-guides/coding_pactice/macros/proc/P.MAC.Proc.06.md))还适用于 prost 这种代码生成库
因此本过程宏规范不仅适用于过程宏,部分规范(例如 [P.MAC.Proc.06](./macros/proc/P.MAC.Proc.06.md))还适用于 prost 这种代码生成库

过程宏必须被单独定义在一个类型为`proc-macro` 的 crate 中。

过程宏有两类报告错误的方式:`Panic` 或 通过 `compile_error` 宏调用发出错误。
过程宏有两类报告错误的方式:`panic` 或 通过 `compile_error` 宏调用发出错误。

过程宏不具有卫生性(hygiene),这意味着它会受到外部语法项(item)的影响,也会影响到外部导入。

Expand Down
4 changes: 2 additions & 2 deletions src/safe-guides/coding_practice/macros/proc/P.MAC.Proc.03.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ quote!(a.to_string())
**【正例】**

```rust
quote!(::std::ToString::to_string(a))
quote!(std::string::ToString::to_string(a))
```

```rust
quote! {{
use ::std::ToString;
use std::string::ToString;
a.to_string()
}}
```
Loading