67、'static lifetime(静态生命周期)
‘static
如果我们尝试在上一个练习中从Vec
借用切片,我们可能会遇到下面的编译器错误:
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 = spawn(move || left.iter().sum::<i32>());
| --------------------------------
argument requires that `v` is borrowed for `'static`
19 | }
| - `v` dropped here while still borrowed
argument requires that v is borrowed for 'static
这一句比较关键,但这是什么意思呢?
在Rust中,‘static lifetime
是一种特殊的生命周期。这意味着该值在整个程序期间都是有效的。
Detached threads(分离线程)
通过thread::spawn
启动的线程,其寿命可能会超过启动这个线程的线程。
例如:
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
生命周期
生成的线程可以:
- 比生成它的线程(父线程)更长寿
- 可以持续运行到程序退出
所以,线程不得借用在程序退出之前可能删除的任何值;违反此规定将会导致use-after-free bug。
这就是为什么std::thread::spawn
的签名要求传递给它的闭包具有‘static
生命周期:
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static
{
// [..]
}
‘static
不只和引用有关
Rust中的所有值都有生命周期,并不仅仅是引用。
特别是,拥有其数据的类型(如Vec
或String
)满足‘static
约束:如果我们拥有它,那么我们就可以一直使用它,甚至在最初创建它的函数return后也是如此。
因此,我们可以把‘static
理解为就是在说:
- 给我一个自有的值
- 给我一个在整个程序期间都有效的引用
第一个说法就是我们上一个练习中解决这个问题的方法:分配新的vector来保存原始vector的左右两部分,然后将它们移到生成的线程中。
‘static
引用
第二种情况,就是作为引用,‘static
在整个程序期间都有效。
static data
最常见的情况是引用静态数据,如字符串字面量。
let s: &'static str = "Hello world!";
由于字符串文字在编译时是已知的,因此Rust将它们存储在可执行文件中,位于称为read-only data segment
(只读数据段)的区域中。因此,只要程序运行,所有指向该区域的引用都将有效;他们满足‘static
约定。
更多内容
练习
题目+答案:
// TODO: Given a static slice of integers, split the slice into two halves and
// sum each half in a separate thread.
// Do not allocate any additional memory!
// TODO: 给定一个静态整数切片,将其拆分为两部分,并在两个独立线程中分别对每部分求和。
// 请勿分配任何额外内存!
use std::thread;
pub fn sum(slice: &'static [i32]) -> i32 {
let res1=thread::spawn(move|| {
let mut result=0;
for i in 0..slice.len()/2 {
result += slice[i];
}
result
});
let res2=thread::spawn(move|| {
let mut result=0;
for i in slice.len()/2..slice.len() {
result += slice[i];
}
result
});
res1.join().unwrap()+res2.join().unwrap()
}
就是完善这个函数。
对比一下官方给的答案:
pub fn sum(slice: &'static [i32]) -> i32 {
let mid = slice.len() / 2;
let (slice1, slice2) = slice.split_at(mid);
let handle1 = thread::spawn(move || slice1.iter().sum::<i32>());
let handle2 = thread::spawn(move || slice2.iter().sum::<i32>());
handle1.join().unwrap() + handle2.join().unwrap()
}
感觉好像其实差不多,就是这个用到了比较高级的split_at
方法。
这一节的学习就先到这里了。