bigywater.tech
Rust · 13

常量、静态变量与 const

const、static 与 static mut 常被混在一起讲。这篇逐个验证:它们在内存里的位置,const fn 能把计算提前到编译期的证据,static mut 为什么需要 unsafe、2024 edition 又加了什么限制,以及安全的可变全局应该怎么写。

mov w0, #6765
const FIB20 = fib(20) 在 -O 下编译成的整个函数体(加 ret)
E0133
访问 static mut 需要 unsafe
Sync
static 的类型必须是 Sync(Cell 不行,原子类型可以)

const 与 static

实测 · 本机执行输出
  源码:
      1 | const LIMIT: u32 = 100;
      2 | static GREETING: &str = "hello";
      3 | static TABLE: [u32; 4] = [1, 2, 4, 8];
      4 | fn main() {
      5 |     println!("const LIMIT = {LIMIT}, static GREETING = {GREETING}, TABLE[3] = {}", TABLE[3]);
      6 |     println!("static TABLE 的地址两次相同吗? {}", &TABLE as *const _ as usize == &TABLE as *const _ as usize);
      7 |     println!("static TABLE 地址 = {:#x}(在可执行文件的数据段)", &TABLE as *const _ as usize);
      8 |     let main_addr = main as *const () as usize;
      9 |     println!("main 函数地址     = {:#x}(代码段)", main_addr);
     10 |     println!("栈上局部变量地址  = {:#x}", &main_addr as *const usize as usize);
     11 | }
  编译:成功
  运行(退出码 0):
    const LIMIT = 100, static GREETING = hello, TABLE[3] = 8
    static TABLE 的地址两次相同吗? true
    static TABLE 地址 = 0x104b4ee54(在可执行文件的数据段)
    main 函数地址     = 0x104b189cc(代码段)
    栈上局部变量地址  = 0x16b2e67d8

const fn 与编译期求值

实测 · 本机执行输出
  fib(20) 作为 const 求值,get() 的汇编: mov	w0, #6765 ; ret
  同样的循环在运行时算(runtime(n),16 条指令): cbz	w0, LBB1_4 ; mov	x9, #0 ; mov	w8, #1 ; LBB1_2: ; add	x10, x9, x8 ; mov	x1, x8 ; mov	x9, x8 ; mov	x8, x10 ...

编译期断言就是这个机制的应用,见编译期断言。

static mut

实测 · 本机执行输出
  源码:
      1 | static mut COUNTER: u32 = 0;
      2 | fn main() {
      3 |     COUNTER += 1;
      4 |     println!("{}", COUNTER);
      5 | }
  编译:失败
    error[E0133]: use of mutable static is unsafe and requires unsafe block
     --> <实验目录>/src/u13c.rs:3:5
      |
    3 |     COUNTER += 1;
      |     ^^^^^^^ use of mutable static
      |
      = note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior
    
    error[E0133]: use of mutable static is unsafe and requires unsafe block
     --> <实验目录>/src/u13c.rs:4:20
      |
    4 |     println!("{}", COUNTER);
      |                    ^^^^^^^ use of mutable static
      |
      = note: mutable statics can be mutated by multiple threads: aliasing violations or data races will cause undefined behavior
    
    error: aborting due to 2 previous errors
    
    For more information about this error, try `rustc --explain E0133`.
  源码:
      1 | static mut COUNTER: u32 = 0;
      2 | fn main() {
      3 |     unsafe { COUNTER += 1; }
      4 |     let v = unsafe { COUNTER };
      5 |     println!("{}", v);
      6 |     unsafe { println!("{}", COUNTER); }
      7 | }
  编译:失败
    error: creating a shared reference to mutable static
     --> <实验目录>/src/u13d.rs:6:29
      |
    6 |     unsafe { println!("{}", COUNTER); }
      |                             ^^^^^^^ shared reference to mutable static
      |
      = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives
      = note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/static-mut-references.html>
      = note: `#[deny(static_mut_refs)]` (part of `#[deny(rust_2024_compatibility)]`) on by default
    
    error: aborting due to 1 previous error

安全的可变全局

实测 · 本机执行输出
  源码:
      1 | use std::sync::atomic::{AtomicUsize, Ordering};
      2 | use std::thread;
      3 | static COUNTER: AtomicUsize = AtomicUsize::new(0);
      4 | fn main() {
      5 |     let hs: Vec<_> = (0..4).map(|_| thread::spawn(|| { for _ in 0..1000 { COUNTER.fetch_add(1, Ordering::Relaxed); } })).collect();
      6 |     for h in hs { h.join().unwrap(); }
      7 |     println!("4 个线程各加 1000 次,COUNTER = {}", COUNTER.load(Ordering::Relaxed));
      8 | }
  编译:成功
  运行(退出码 0):
    4 个线程各加 1000 次,COUNTER = 4000
  源码:
      1 | use std::cell::Cell;
      2 | static C: Cell<i32> = Cell::new(0);
      3 | fn main() { C.set(1); }
  编译:失败
    error[E0277]: `Cell<i32>` cannot be shared between threads safely
     --> <实验目录>/src/u13f.rs:2:11
      |
    2 | static C: Cell<i32> = Cell::new(0);
      |           ^^^^^^^^^ `Cell<i32>` cannot be shared between threads safely
      |
      = help: the trait `Sync` is not implemented for `Cell<i32>`
      = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicI32` instead
      = note: shared static variables must have a type that implements `Sync`
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0277`.

原子类型与内存顺序见原子类型与内存顺序;Sync 的含义见Send 与 Sync。

分步演示

const 与 static
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。汇编用 --emit=asm -O 生成,目标 aarch64-apple-darwin;地址是单次运行的数字
    • 没有验证:const 在每个使用处被内联(按 Rust 参考手册的说法,没有逐处验证)、static 的初始化表达式的限制、thread_local!、OnceLock / LazyLock、const 泛型
    京ICP备2026067502号-1京公网安备11010502063487号