bigywater.tech
Rust · 26

'static 生命周期

'static 经常被误解成「永远活着」。这篇用真实的编译器行为纠正它:什么样的值满足 'static 约束、thread::spawn 为什么要求它、有哪些绕开办法,以及 Box::leak 得到 &'static 的真实成本。

T: 'static
不含非 'static 引用(String 满足),不是「永远活着」
E0373
thread::spawn 的闭包借用局部变量
dealloc +0
1000 次 Box::leak 之后的释放次数

&'static 的来源

实测 · 本机执行输出
  源码:
      1 | static GLOBAL: &str = "全局";
      2 | fn lit() -> &'static str { "字面量" }
      3 | fn leaked() -> &'static mut Vec<i32> { Box::leak(Box::new(vec![1, 2, 3])) }
      4 | fn main() {
      5 |     println!("{} {}", GLOBAL, lit());
      6 |     let v = leaked();
      7 |     v.push(4);
      8 |     println!("Box::leak 得到的 &'static mut:{:?}", v);
      9 | }
  编译:成功
  运行(退出码 0):
    全局 字面量
    Box::leak 得到的 &'static mut:[1, 2, 3, 4]

T: 'static 约束

实测 · 本机执行输出
  源码:
      1 | fn needs_static<T: 'static + std::fmt::Debug>(t: T) { println!("收到 {:?}", t); }
      2 | fn main() {
      3 |     let owned = String::from("owned");
      4 |     needs_static(owned);                 // 拥有数据:满足 'static
      5 |     needs_static(5);                     // 整数:满足
      6 |     needs_static("literal");             // &'static str:满足
      7 | }
  编译:成功
  运行(退出码 0):
    收到 "owned"
    收到 5
    收到 "literal"
  源码:
      1 | fn needs_static<T: 'static + std::fmt::Debug>(t: T) { println!("收到 {:?}", t); }
      2 | fn main() {
      3 |     let s = String::from("local");
      4 |     needs_static(&s);
      5 | }
  编译:失败
    error[E0597]: `s` does not live long enough
     --> <实验目录>/src/u26c.rs:4:18
      |
    3 |     let s = String::from("local");
      |         - binding `s` declared here
    4 |     needs_static(&s);
      |     -------------^^-
      |     |            |
      |     |            borrowed value does not live long enough
      |     argument requires that `s` is borrowed for `'static`
    5 | }
      | - `s` dropped here while still borrowed
      |
    note: requirement that the value outlives `'static` introduced here
     --> <实验目录>/src/u26c.rs:1:20
      |
    1 | fn needs_static<T: 'static + std::fmt::Debug>(t: T) { println!("收到 {:?}", t); }
      |                    ^^^^^^^
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0597`.

thread::spawn

实测 · 本机执行输出
  源码:
      1 | use std::thread;
      2 | fn main() {
      3 |     let v = vec![1, 2, 3];
      4 |     let h = thread::spawn(|| println!("{:?}", v));
      5 |     h.join().unwrap();
      6 | }
  编译:失败
    error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function
     --> <实验目录>/src/u26d.rs:4:27
      |
    4 |     let h = thread::spawn(|| println!("{:?}", v));
      |                           ^^                  - `v` is borrowed here
      |                           |
      |                           may outlive borrowed value `v`
      |
    note: function requires argument type to outlive `'static`
     --> <实验目录>/src/u26d.rs:4:13
      |
    4 |     let h = thread::spawn(|| println!("{:?}", v));
      |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword
      |
    4 |     let h = thread::spawn(move || println!("{:?}", v));
      |                           ++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0373`.
  源码:
      1 | use std::{sync::Arc, thread};
      2 | fn main() {
      3 |     let v = vec![1, 2, 3];
      4 |     let h = thread::spawn(move || println!("move 之后线程拥有 v:{:?}", v));        // 解法 1:move
      5 |     h.join().unwrap();
      6 |     let shared = Arc::new(vec![4, 5, 6]); let s2 = Arc::clone(&shared);
      7 |     thread::spawn(move || println!("解法 2:Arc 共享 {:?}", s2)).join().unwrap();
      8 |     let local = vec![7, 8, 9];
      9 |     thread::scope(|s| { s.spawn(|| println!("解法 3:thread::scope 可以借用局部变量 {:?}", local)); });
     10 | }
  编译:成功
  运行(退出码 0):
    move 之后线程拥有 v:[1, 2, 3]
    解法 2:Arc 共享 [4, 5, 6]
    解法 3:thread::scope 可以借用局部变量 [7, 8, 9]

spawn 与线程的更多内容见线程创建与 Join;异步里的 spawn 与 'static 见spawn 与 'static。

Box::leak

实测 · 本机执行输出
  源码:
      1 | use std::alloc::{GlobalAlloc, Layout, System};
      2 | use std::sync::atomic::{AtomicUsize, Ordering};
      3 | struct C; static AL: AtomicUsize = AtomicUsize::new(0); static DE: AtomicUsize = AtomicUsize::new(0);
      4 | unsafe impl GlobalAlloc for C {
      5 |     unsafe fn alloc(&self, l: Layout) -> *mut u8 { AL.fetch_add(1, Ordering::Relaxed); unsafe { System.alloc(l) } }
      6 |     unsafe fn dealloc(&self, p: *mut u8, l: Layout) { DE.fetch_add(1, Ordering::Relaxed); unsafe { System.dealloc(p, l) } }
      7 | }
      8 | #[global_allocator] static G: C = C;
      9 | fn main() {
     10 |     let (a0, d0) = (AL.load(Ordering::Relaxed), DE.load(Ordering::Relaxed));
     11 |     for i in 0..1000 { let b = Box::new([i as u8; 64]); std::hint::black_box(&b); }          // 释放
     12 |     let (a1, d1) = (AL.load(Ordering::Relaxed), DE.load(Ordering::Relaxed));
     13 |     for i in 0..1000 { let b: &'static mut [u8; 64] = Box::leak(Box::new([i as u8; 64])); std::hint::black_box(&b); }   // 泄漏
     14 |     let (a2, d2) = (AL.load(Ordering::Relaxed), DE.load(Ordering::Relaxed));
     15 |     println!("1000 个 Box 正常 drop:alloc +{},dealloc +{}", a1 - a0, d1 - d0);
     16 |     println!("1000 个 Box::leak     :alloc +{},dealloc +{}(从不释放)", a2 - a1, d2 - d1);
     17 | }
  编译:成功
  运行(退出码 0):
    1000 个 Box 正常 drop:alloc +1000,dealloc +1000
    1000 个 Box::leak     :alloc +1000,dealloc +0(从不释放)

分步演示

'static
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。计数分配器在 release 构建下统计
    • 没有验证:&'static 与 const 提升(rvalue static promotion)、泄漏的内存是否被 Miri 之类工具报告、lazy_static / OnceLock 的实现、'static 与 Send 的关系
    京ICP备2026067502号-1京公网安备11010502063487号