Skip to content

29.8 全局变量

Rust 中允许存在全局变量。在基本语法章节讲过,使用 static 关键字修饰的变量就是全局变量。全局变量有一个特点:如果要修改全局变量,必须使用 unsafe 关键字:

rust
static mut G: i32 = 1;

fn main() {
    G = 2;
}

编译,可见错误信息为:

txt
error[E0133]: use of mutable static requires unsafe function or block

这个规定显然是有助于线程安全的。如果允许任何函数可以随便读写全局变量的话,线程安全就无从谈起了。只有不用 mut 修饰的全局变量才是安全的,因为它只能被读取,不能被修改。

我们又可以想到,有些类型的变量不用 mut 修饰,也是可以做修改的。比如具备内部可变性的 Cell 等类型。我们可以试验一下,如果有一个全局的、具备内部可变性的变量,会发生什么情况:


rust
use std::cell::Cell;
use std::thread;

static G: Cell<i32> = Cell::new(1);

fn f1() {
    G.set(2);
}

fn f2() {
    G.set(3);
}

fn main() {
    thread::spawn( || { f1() } );
    thread::spawn( || { f2() } );
}

试着编译一下上面这个例子,可见编译错误为:


rust
error[E0277]: the trait bound `std::cell::Cell<i32>: std::marker::Sync` is not satisfied
--> test.rs:5:1
    |
5 | static G: Cell<i32> = Cell::new(1);
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `std::cell::Cell<i32>` cannot be shared between threads safely
    |
    = help: the trait `std::marker::Sync` is not implemented for `std::cell:: Cell<i32>`
    = note: shared static variables must have a type that implements `Sync`

对于上面这个例子,我们可以推理一下,现在有两个线程同时修改一个全局变量,而且修改过程没有做任何线程同步,这里肯定是有线程安全的问题。但是,注意这里传递给 spawn 函数的闭包,实际上没有捕获任何局部变量,所以,它是满足 Send 条件的。在这种情况下,线程不安全类型并没有直接穿越线程的边界,spawn 函数这里指定的约束条件是查不出问题来的。

但是,编译器还设置了另外一条规则,即共享又可变的全局变量必须满足 Sync 约束。根据 Sync 的定义,满足这个条件的全局变量显然是线程安全的。因此,编译器把这条路也堵死了,我们不可以简单地通过全局变量共享状态来构造出线程不安全的行为。对于那些满足 Sync 条件且具备内部可变性的类型,比如 Atomic 系列类型,作为全局变量共享是完全安全且合法的。

Released under the MIT License