Skip to content
Merged
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
58 changes: 39 additions & 19 deletions src/safe-guides/coding_practice/statics.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

**【描述】**

对可变静态变量直接进行全局修改是 Unsafe 的。在多线程应用中,修改静态变量会导致数据争用(data race),此未定义行为目前并不会被Clippy或Rustc检测出
对可变静态变量直接进行全局修改是 Unsafe 的。在多线程应用中,修改静态变量会导致数据争用(data race),此未定义行为目前并不会被 Clippy 或 Rustc 检测出

**【反例】**

Expand All @@ -28,7 +28,7 @@ unsafe fn eat_apple() {

**【正例】**

若需要变更的值的类型为整数或布尔时,可直接使用atomic
若需要变更的值的类型为整数或布尔时,可直接使用 atomic

```rust
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
Expand All @@ -46,31 +46,51 @@ fn eat_apple() {

**【正例】**

若需修改整数或布尔之外的数据类型时,可考虑使用Mutex或Rwlock配合lazy_static宏的方式对全局变量进行变更。
若需修改整数或布尔之外的数据类型时,可考虑使用 Mutex 或 Rwlock 配合 once_cell 对全局变量进行变更。

(注: once_cell 目前已经被引入到 Nightly 版本的标准库中但还不稳定, 可参考 [std::lazy](https://doc.rust-lang.org/std/lazy/index.html)。若要在 Stable 版本下使用,则需要引入第三方库 [once_cell](https://docs.rs/once_cell/latest/once_cell/)。)

```rust
#![feature(once_cell)] // 需要nightly compiler

use std::sync::Mutex;
use lazy_static::lazy_static;
use std::lazy::SyncLazy; // 若使用stable版本则需要将之替换为once_cell::sync::Lazy

lazy_static! {
static ref MESSAGE: Mutex<String> = Mutex::new(String::from("I'm hungry"));
}
static GLOBAL_MESSAGE: SyncLazy<Mutex<String>> = SyncLazy::new(|| {
Mutex::new(String::from("I'm hungry"))
});

fn update_msg() {
let mut old_msg = MESSAGE.lock().unwrap();
*old_msg = String::from("I'm not hungry anymore!");
fn update_msg(msg: &str) {
let mut old_msg = GLOBAL_MESSAGE.lock().unwrap();
*old_msg = msg.to_string();
}

fn main() {
println!("{}", MESSAGE.lock().unwrap()); // I'm hungry
update_msg();
println!("{}", MESSAGE.lock().unwrap()); // I'm not hungry anymore!
println!("{}", GLOBAL_MESSAGE.lock().unwrap()); // I'm hungry
update_msg("I'm not hungry anymore!");
println!("{}", GLOBAL_MESSAGE.lock().unwrap()); // I'm not hungry anymore!
}
```

上述示例亦可通过使用第三方库 [lazy_static](https://docs.rs/lazy_static/latest/lazy_static/) 的方式实现。

```rust
use std::sync::Mutex;
use lazy_static::lazy_static;

lazy_static! {
static ref GLOBAL_MESSAGE: Mutex<String> = Mutex::new(String::from("I'm hungry"));
}

fn update_msg(msg: &str) {
...
}
...
```

**【例外】**

在使用FFI引用外部,例如C的函数时,其本身有可能会返回全局变量。当rust接入这些函数时需要指定输入的变量类型为静态(static),而若要改变它们的值的时候就需要将其定义为可变静态变量(static mut)。
在使用FFI引用外部,例如C的函数时,其本身有可能会返回全局变量。当 rust 接入这些函数时需要指定输入的变量类型为静态(static),而若要改变它们的值的时候就需要将其定义为可变静态变量(static mut)。

```rust
use std::ffi::CString;
Expand All @@ -85,17 +105,17 @@ fn main() {
let prompt = CString::new("[my-awesome-shell] $").unwrap();
unsafe {
rl_prompt = prompt.as_ptr();

println!("{:?}", rl_prompt);

rl_prompt = ptr::null();
}
}
```

**【例外】**

通常情况下直接修改static mut会有线程安全风险,但若配合使用[std::sync::Once](https://doc.rust-lang.org/std/sync/struct.Once.html#)则可保证该变量只初始化一次,不会产生线程安全风险。
通常情况下直接修改 static mut 会有线程安全风险,但若配合使用 [std::sync::Once](https://doc.rust-lang.org/std/sync/struct.Once.html#) 则可保证该变量只初始化一次,不会产生线程安全风险。

(注:此用法在功能上等同于 [once_cell::sync::OnceCell](https://docs.rs/once_cell/latest/once_cell/sync/struct.OnceCell.html) 或 Nightly 版本中的 [std::lazy::SyncOnceCell](https://doc.rust-lang.org/std/lazy/struct.SyncOnceCell.html)。但在使用 Stable 版本编译器并且不使用第三方库的条件下此写法完全合规,故算作例外情况。)

```rust
use std::sync::{Mutex, Once};
Expand Down Expand Up @@ -127,6 +147,6 @@ fn main() {

这条规则如果需要定制 Lint,则应考虑两种情况:

1. 定义为static mut的变量是否被用于FFI
2. 定义为static mut的变量在仅被用在call_once或call_once_force等方法的闭包内
1. 代码中定义为 static mut 的变量是否仅被用于 FFI
2. 代码中定义为 static mut 的变量是否经过 call_once 初始化