'static

如果你尝试从前一个练习中的向量借用切片,你可能遇到了类似这样的编译器错误:

error[E0597]: `v` does not live long enough
   |
11 | pub fn sum(v: Vec<i32>) -> i32 {
   |            - binding `v` declared here
...
15 |     let right = &v[split_point..];
   |                  ^ borrowed value does not live long enough
16 |     let left_handle = thread::spawn(move || left.iter().sum::<i32>());
   |                        ------------------------------------------------ 
                          argument requires that `v` is borrowed for `'static`
19 | }
   |  - `v` dropped here while still borrowed

“参数要求v被借用为'static”,这是什么意思呢?

'static生命周期在Rust中是一个特殊的存在。它意味着这个值在整个程序的运行期间都是有效的。

脱离的线程

通过thread::spawn启动的线程可以比创建它的线程更持久。例如:

#![allow(unused)]
fn main() {
use std::thread;

fn f() {
    thread::spawn(|| {
        thread::spawn(|| {
            loop {
                thread::sleep(std::time::Duration::from_secs(1));
                println!("Hello from the detached thread!");
            }
        });
    });
}
}

在这个示例中,第一个生成的线程会进一步生成一个子线程,每秒打印一条消息。然后,第一个线程会完成并退出。当这种情况发生时,其子线程会继续运行,只要整个程序还在运行。在Rust的术语中,我们说子线程已经超过了其父线程的生命周期。

'static生命周期

既然一个生成的线程能够:

  • 比生成它的线程(父线程)存活更久
  • 运行到程序结束

那么它就不能借用任何可能在程序结束前被丢弃的值;违反这一约束可能会使我们面临使用已释放内存的问题。这就是std::thread::spawn的签名要求传递给它的闭包具有'static生命周期的原因:

#![allow(unused)]
fn main() {
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
    F: FnOnce() -> T + Send + 'static,
    T: Send + 'static
{
    // [..]
}
}

'static不仅仅关于引用

Rust中所有值都有生命周期,不仅仅是引用。

特别是,一个拥有自己数据的类型(如VecString)满足'static约束:如果你拥有它,即使最初创建它的函数已经返回,你也可以随意处理它,想处理多久就处理多久。

因此,你可以将'static理解为一种表达方式:

  • 给我一个拥有权值
  • 给我一个在整个程序期间都有效的引用

第一种方法就是你在前一个练习中解决问题的方法:通过分配新的向量来持有原始向量的左右部分,然后将它们移动到生成的线程中。

'static引用

现在讨论第二种情况,即在整个程序期间都有效的引用。

静态数据

最常见的案例是指向静态数据的引用,比如字符串字面量:

#![allow(unused)]
fn main() {
let s: &'static str = "你好,世界!";
}

由于字符串字面量在编译时已知,Rust会将它们存储在可执行文件的内部,一个称为只读数据段的区域。因此,指向该区域的所有引用都会在程序运行期间保持有效;它们满足'static要求。

进一步阅读

  • 数据段(英文维基百科页面,解释了程序的静态数据存储区域)