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中的所有值都有生命周期,并不仅仅是引用。

特别是,拥有其数据的类型(如VecString)满足‘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方法。

 

这一节的学习就先到这里了。

阅读剩余
THE END