'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中所有值都有生命周期,不仅仅是引用。
特别是,一个拥有自己数据的类型(如Vec
或String
)满足'static
约束:如果你拥有它,即使最初创建它的函数已经返回,你也可以随意处理它,想处理多久就处理多久。
因此,你可以将'static
理解为一种表达方式:
- 给我一个拥有权值
- 给我一个在整个程序期间都有效的引用
第一种方法就是你在前一个练习中解决问题的方法:通过分配新的向量来持有原始向量的左右部分,然后将它们移动到生成的线程中。
'static
引用
现在讨论第二种情况,即在整个程序期间都有效的引用。
静态数据
最常见的案例是指向静态数据的引用,比如字符串字面量:
#![allow(unused)] fn main() { let s: &'static str = "你好,世界!"; }
由于字符串字面量在编译时已知,Rust会将它们存储在可执行文件的内部,一个称为只读数据段的区域。因此,指向该区域的所有引用都会在程序运行期间保持有效;它们满足'static
要求。
进一步阅读
- 数据段(英文维基百科页面,解释了程序的静态数据存储区域)