Rust标准库创建线程的函数有如下签名:
pub fn spawn<F, T>(self, f: F) -> io::Result<JoinHandle<T>> where
F: FnOnce() -> T, F: Send + 'static, T: Send + 'static
可以看到,spawn函数对泛型参数F
和T
都有Send + ’static
约束,先不讨论这里的Send
约束,重点关注对泛型参数F
和T
的’static
生命周期约束。看下面的一个例子:
use std::thread;
fn test() {
let x = &666;
let _ = thread::spawn(move|| x).join();
}
fn main() {
test();
}
这个例子不能通过编译。编译器提示:
error: borrowed value does not live long enough
在函数test
中有一个借用类型的栈变量x,我们预期能把它move到一个新创建的线程中,但是编译器阻止了我们的动作。这是因为Rust标准库创建的新线程是与创建它的父线程是detach模式的,也就是说,子线程脱离了父线程的运行环境;当test
函数运行完毕时,借用型变量x
的生命周期结束,然而我们新创建的线程在test
函数运行完毕时不一定被调度执行。这时,如果x
被move进新线程,这可能会引起use-after-free
的内存安全问题。Rust是强调内存安全的语言,她会在编译时就阻止我们这样做。
编译器为什么能这么智能的阻止我们的这种不安全的行为呢?这里对泛型参数'F
和'T
的'static
生命周期约束起了关键作用。'static
生命周期约束从字面意思上理解,由她限定的类型在整个程序运行的过程中都不会被析构。或者另外一种理解:创建新线程时的closure
参数在捕获外部变量的时候,如果外部变量是引用类型,或者外部变量内部包含引用类型,那么这些引用类型的生命周期应该不小于'static
。
正如参考链接1中所说:
The 'static bound on a type doesn't control how long that object lives; it controls the allowable lifetime of references that object holds.
对某一类型的'static
约束不是用来控制这个类型的对象可以存活多久;'static
约束控制的是这个类型的对象所包含的引用类型所允许的生命周期。 看下面这个例子:
struct TestStruct(i32);
trait Hello: Sized {
fn say_hello(&self) {
println!("hello");
}
}
impl Hello for TestStruct{}
fn test1<T: Hello + 'static + Sized>(t: &T) {
t.say_hello();
}
fn test2() {
let x = TestStruct(666);
test1(&x);
}
fn main(){
test2();
}
这个例子中,test2
函数有一个栈变量x
, x
在test2
调用结束时就会被释放,它的生命周期并不是程序运行的整个过程。x
的引用被传递到test1
函数中。尽管对类型T
有'static
约束,但是程序是可以正常运行的,即x
没有违背对它的'static
约束,否则程序不会编译通过。我们再看一个类型中含有引用类型的例子:
struct TestStruct<'a>(&'a i32);
trait Hello: Sized {
fn say_hello(&self) {
println!("hello");
}
}
impl<'a> Hello for TestStruct<'a>{}
fn test1<T: Hello + 'static + Sized>(t: &T) {
t.say_hello();
}
fn test2() {
let y = 666;
let x = TestStruct(&y);
test1(&x);
}
fn main(){
test2();
}
正如我们预期的那样会出现编译错误
error:
y
does not live long enough
当去掉test
函数中对类型T
的'static
约束时,程序编译运行正常,输出hello
。
至此,总算明白了'static
生命周期约束在Rust中的作用。总结起来就是参考链接中提到的两点:
-
'static
生命周期约束不是用来控制对象的存活时间; -
'static
生命周期约束的是对象里包含的引用变量的生命周期;
网友评论