Rust's lifetime
和其他语言不一样的,Rust 中的 lifetime 是可以显式定义出来帮助编译器进行判断的;为什么要显式的 Lifetime 呢?主要还是为了解决内存的引用的问题,其他的语言,尤其是 C++ 有大部分的问题都源于使用无效的内存或者已经释放的内存,这种情况在 C++ 语言中属于 undefine 的行为,所以就导致程序有的时候运行的好好的有的时候就发生莫名的错误,查类似的问题非常消耗时间和精力,而 Rust 就通过生命周期来彻底解决这个问题的。那么 C++ 没有这个概念吗?有,但是不强制,编译器也不检查。
Rust 中的生命周期和其他的语言一样,主要表示的当前变量能存活的时间范围;在 Rust 中每一个 borrow/ref 都会带上生命周期变量,为什么只是对 ref 强制呢,因为 ref 本质上是借用,而借用就可能会产生借用本身的生命周期大于 owner 本身,这就会导致内存问题,所以在 Rust 中所有的 reference 都是会有自己的 lifetime 的,但是很多时候编译器是会自动通过推导来计算出来的,所以我们看不到的,但是当编译器无法推断结果的时候就会报错,然后让程序员加入 lifetime 变量来帮助编译器推导。
1. 编译器推导三个准则
函数或者方法的参数的生命周期被成为输入生命周期,返回值的生命周期成为输出生命周期
- 每一个引用参数都有它自己的生命周期参数,也就是如果一个函数或者方法有多个参数就有多个生命周期参数;
fn foo<'a>(x: &'a i32)一个生命周期参数,fn foo<'a, 'b>(x :&'a i32, c: &'b i32): 两个生命周期参数 - 如果函数的参数只有一个 (一个输入参数),那么输出参数的生命周期都以输入参数的生命周期为准;
- 如果是方法的话,所有的输出参数的周期以 self 的生命周期为准;
2. 使用
- 最长的字符串长度
// 编译器会报错,说返回值缺少一个 lifetime,主要是因为编译器不知道返回值的 lifetime 是什么,按照上面三个规则,没有一个能命中,所以编译器报错的,于是程序员就需要来处理了
fn get_largest_str(left :&str, right :&str) -> &str {
if left.len() > right.len() {
left
} else {
right
}
}
//定义一个生命周期的变量,'a,这个'a 是输入参数 left 和 right 中重叠的 lifetime,也就是这两个参数中最小的那个 lifetime
// 通过这么写之后编译器就知道将返回值赋值与一些变量的时候是否符合借用的要求;
fn get_largest_str<'a>(left :&'a str, right :&'a str) -> &'a str {
if left.len() > right.len() {
left
} else {
right
}
}
- struct 中使用借用变量
``` Rust implementation // 对于使用借用变量的结构体,必须指明 lifetime,因为只有这样才能让编译器发现结构体的生命周期和借用的生命周期,struct 变量绝对不能比借用的生命周期要长; struct RefStruct<'a> { part : &'a str, }
impl<'a> RefStruct<'a> { //不需要指明生命周期,原因是第三条规则; fn level(&self) -> i32 { 3 } }
* static 生命周期
`'static`表示其生命周期是存在于整个程序期间的;
``` Rust
let s :&'static str = "i have an apple";
'static被用于 trait bound 的语意文档
在这种情况的 static 其实本质含义是:当前的参数是具有 ownership,而非 borrow,也就是说参数必须是具有 ownership 的,不能是借用的模式;
//表示 input 必须具有 ownership,不能是借用
fn print_it(input : impl std::fmt::Debug + 'static) {
println!("{:?}", input);
}
#[test]
fn test_static() {
let a = String::from("hello");
print_it(a);
let b = String::from("hello");
print_it(&b); // error,编译器报错:** borrowed value does not live long enough **
}
3. 总结
从根本上来,为了能让编译器去帮我们判断程序中有哪些内存问题,编译器就必须知道一个变量引用另外的变量是否合法,这种合法性就需要通过 lifetime 来完成。编译器本身能自己完成一些,但是有一些必然是完成不了的,这个时候编译器就会求助与程序员,让程序员去加入更多的推测信息,通过这种强制语法来保证最后出来的程序是合法合规的,这种其实我能接受,而且我喜欢 Rust 的这种明确的感觉,什么的确都很明确,而不像 C++ 很多不明确的行为,需要程序员通过各种方式去规避,编译器没有尝试去帮助我们。
之前一直不明白为什么下面这个语句有问题?
let arr = vec![1];
thread::spawn(||{
println!("{:?}", arr);
});
现在我明白了,对于 Rust 来说,lambda 本身默认使用的借用的方式去捕获变量,但是生成线程本身的 lifetime 可能是大于这个变量存在的 lifetime,所以必然会编译错误; 这也是 Rust 本身比较严谨的一个方面。
好了,lifetime 讲完了,其实这个东西不难理解,但是之前的语言并不会这么来用,所以做一篇笔记保留一下,后面的时候可以继续回顾。