非词法作用域
Rust 的生命周期检查为静态检查, 并且为非词法作用域。
Rust把生命周期检查的步骤由HIR改为了MIR,以便可以降低生命周期检查的粒度,使生命周期规则从词法作用域转变成非词法作用域
1
2
3
4
5
| let mut data = vec![1, 2, 3];
let x = &data[0];
println!("{}", x);
// This is OK, x is no longer needed
data.push(4);
|
但如果 x 实现了 Drop 会怎么样呢?
1
2
3
4
5
6
7
8
9
10
11
12
| #[derive(Debug)]
struct X<'a>(&'a i32);
impl Drop for X<'_> {
fn drop(&mut self) {}
}
let mut data = vec![1, 2, 3];
let x = X(&data[0]);
println!("{:?}", x);
data.push(4);
// Here, the destructor is run and therefore this'll fail to compile.
|
当实现 Drop 时,生成的代码会隐式的添加 drop 调用,实际的生命周期比看到的非词法作用域要长。
static
static lifetime 和 static bound 是不同的概念。
&'static
表示被引用的对象具有 static 生命周期,被引用对象必须要活得跟剩下的程序一样久。描述的是被引用对象,而不是引用自身。至于对象是什么时候创建的并不重要,可以是编译时的 static 对象,也可以是动态创建的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 字符串常量
let str_literal: &'static str = "str literal";
// 静态全局变量
static mut MUT_BYTES: [u8; 3] = [1, 2, 3];
// 运行时创建的对象,但生命周期和整个程序一样长
fn rand_str_generator() -> &'static str {
let rand_string = rand::random::<u64>().to_string();
Box::leak(rand_string.into_boxed_str())
}
fn static_life(t: &'static str) {}
static_life("123");
// 编译报错:argument requires that borrow lasts for `'static`
// static_life(&String::from("123"));
|
T: 'static
表示的是 T 被 static bound 约束,T 持有的对象可以一直到程序结束,但并不是必须活的和程序一样久。String,Vec 等 owner 对象满足 static bound,因为持有这种对象无限长是安全的,他们内部没有引用其他对象。如果 T
内部包含了其他对象的引用,能够被无限持有还需要看内部被引用对象的生命周期。
1
2
3
4
5
6
7
8
9
10
11
12
| fn drop_static<T: 'static>(t: T) {
std::mem::drop(t);
}
// owned 的 String
drop_static(String::from("hello"));
fn static_bound<T: 'static>(t: &T) {}
static_bound(&String::from("123"));
static_bound(&Box::new("123"));
let s = String::from("123");
// 编译报错:argument requires that `s` is borrowed for `'static`
// static_bound(&Box::new(&s));
|
&'static T
和 T: 'static
的关系:
&'static T
满足 static bound。- 满足 static bound 的不一定需要是 static 生命周期
生命周期案例分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| struct Text<'txt> {
content: &'txt str
}
struct RefText<'txt> {
ptr: &'txt mut Text<'txt>
}
fn main() {
let mut txt = Text {
content: "eeee",
};
{
let r = RefText {
ptr: &mut txt,
};
}
use_text(&mut txt);
}
fn use_text(_list: &mut Text) {
}
error[E0499]: cannot borrow `txt` as mutable more than once at a time
--> src/main.rs:19:14
|
16 | ptr: &mut txt,
| -------- first mutable borrow occurs here
...
19 | use_text(&mut txt);
| ^^^^^^^^
| |
| second mutable borrow occurs here
| first borrow later used here
|
将生命周期展开:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| 'a: {
let content: &'a str = "eeee";
let mut txt: Text<'a> = Text<'a> {
content: content,
};
'b: {
// 由于 RefText 定义的限制,这里 ptr 的生命周期不能是 'b
// let ptr: &'b mut Text<'a> = &'b mut txt;
let ptr: &'a mut Text<'a> = &'a mut txt;
let r: RefText<'a> = RefText<'a> {
ptr: ptr,
};
}
// a 的生命周期内,已经被借用了
'c: {
use_text(&'c mut txt);
}
}
|
如果是如下的形式,将不存在上面的问题:
1
2
3
| struct RefText<'ptr, 'txt> {
ptr: &'ptr mut Text<'txt>
}
|
推导 ptr
类型时,为什么不能是 &'b mut Text<'b>
? 涉及到协变和逆变问题:
对于可变引用&'a mut T
,对 'a
是可以协变的,即你用一个比 'a
活得久的赋值给它是 OK 的,但是 T
是不可形变的。如上面的 ptr
,它对 T
为 Text<'a>
是不可形变的,而 ptr
自身的生命周期可以协变,所以 ptr
可以是 &'b mut Text<'a>
或 &'a mut Text<'a>
,而不能是 &'b mut Text<'b>
。
Reference