前言
先说大白话,rust 的生命周期标注,是为了明确多个变量的生命周期是否一致,仅此而已,因为如果rust不知道多个变量的生命周期是否一致,它无法确的知道这个变量是否已经被释放。这个下面再细说,先说有什么用。
rust当中,的两个重要概念:借用和生命周期分别代是在:
- 栈变量,需要关注【所有权】
- 引用(指针),需要关注【生命周期】
Rust 的每个引用都有自己的生命周期,生命周期指的是引用保持有效的作用域。 大多数情况下,引用是隐式的、可以被推断出来的,但当引用可能以不同的方式互相关联时,则需要手动标注生命周期。 这里重点就是以不同的方式互相关联时。
大多数情况下,rust 可以自己推断出引用的生拿周期,也就是只有在一些rust无法自行推断的情况下,才需要手动标注生命周期。
生命周期
Rust 中的每一个引用都有其生命周期(lifetime),也就是引用保持有效的作用域。 大部分时候生命周期是隐含并可以推断的,正如大部分时候类型也是可以推断的一样。 类似于当因为有多种可能类型的时候必须注明类型,也会出现引用的生命周期以一些不同方式相关联的情况,所以 Rust 需要我们使用泛型生命周期参数来注明他们的关系,这样就能确保运行时实际使用的引用绝对是有效的。
这里还有一个需要关注的点就是关系,也就多个引用之前的关系,才是导致rust无法明确推断出引用生命周期的最根本原因。
反例
这段代码看着很正常,但是实际上,编译会报错,类为这里调用longest
时,longest
无法确认x
、y
的生命周期。
为什么无法确认?
因为longest
是被调用的方法,它肯定没法知道,这两个传入在main
方法的中的生命周期。
好比,你写一个接口给外部调用,你也无法知道调你的服务,传入的两个变量,在那个服务中的生命周期。
但是在rust中,又非常强调安全性,它必须清楚每个引用的明确的生命周期。
所以这个活,就落在了开发者身上,必须明确告诉rust,每个引用的生命周期。
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
// longest函数 无法确认 x、y 在 mian 函数中的生命周期
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
报错如下:missing lifetime specifier
代码语言:javascript复制error[E0106]: missing lifetime specifier
--> src/main.rs:9:33
|
9 | fn longest(x: &str, y: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
|
9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
For more information about this error, try `rustc --explain E0106`.
error: could not compile `playground` due to previous error
上面看着很正常呀,哪里有问题?
生命周期标注
即然rust
不智能,那只能开发者辛苦一点,手动来标注了。
rust
的生命周期标注语法,只能表示引用的生命周期,而不能、不会改会引用的生命周期。
命名规则:
'a
以 ' 开头- 全小写
&i32 // 引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用
单个的生命周期注解本身没有多少意义,因为生命周期注解告诉 Rust 多个引用的泛型生命周期参数如何相互联系的。
函数签名中的生命周期注解
描述了 x、y 之间的关系。
longest 函数定义指定了签名中所有的引用必须有相同的生命周期'a
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
只有一个参数,要不要标注
那当然是不要啦!! 生命周期注解告诉编译器引用参数的有效范围,以便编译器可以检查代码是否合法。 但是,在某些情况下,编译器可以自动推断出引用参数的生命周期,因此不需要显式注解。
当一个函数或方法需要一个借用参数时,如果该参数的生命周期与函数或方法的生命周期相同,则可以省略生命周期注解。例如: 这个例子,标不标注都是成立的。
代码语言:javascript复制fn foo<'a>(x: &'a i32) -> &'a i32 {
x
}
fn main() {
let x = 5;
let y = foo(&x);
println!("{}", y);
}
但是,如果函数或方法需要一个借用参数,并且该参数的生命周期与函数或方法的生命周期不同,则必须显式注解参数的生命周期。
代码语言:javascript复制struct Foo<'a> {
x: &'a i32,
}
impl<'a> Foo<'a> {
fn bar(&self, y: &'a i32) -> &'a i32 {
if *y > 0 {
y
} else {
self.x
}
}
}
fn main() {
let x = 5;
let y = 6;
let foo = Foo { x: &x };
let z = foo.bar(&y);
println!("{}", z);
}
在这个例子中,方法 bar 的第二个参数 y 的生命周期不同于 Foo 结构体中的引用 x 的生命周期,所以嘛必须显式注解参数的生命周期。
总结
人多了,就容易产生纠分,变量形参多了,也是这样,所以才需要标注,分个明白。