生命周期
Rust 中的每一个引用都有其生命周期(lifetime),也就是引用保持有效的作用域。大多数生命周期是隐含并且可以推断的。对于一些无法推断的场景需要使用泛型生命周期参数来注明他们的关系。
生命周期避免了悬垂引用,引用了非预期引用的数据。变量已经释放但是还持有该变量地址的引用,里边的数据可能已经被其他变量使用。
借用检查器 (borrow checker) 检查 r 的生命周期 ’a 引用了 生命周期 ’b 的对象 x,所以拒绝编译。因为 ’b 的生命周期 比 ’a 的生命周期小的多。
fn main() { let r; // -------------+- 'a { let x = 5; // -----+- 'b | r = &x; // -----+| 'b | } println!("{r}"); // -------------+- 'a }
fn main() { let r; // -------------+- 'a { let x = 5; // -----+- 'b | r = &x; // | | println!("{r}"); // ------------- | 'a // -----| 'b | } println!("{r}"); // --------------| 'a 检查无效 }
函数中标注生命周期参数
生命周期注解并不改变任何引用的生命周期的长短。相反它们描述了多个引用生命周期相互的关系,而不影响其生命周期。与当函数签名中指定了泛型类型参数后就可以接受任何类型一样,当指定了泛型生命周期后函数也能接受任何生命周期的引用。
生命周期参数名称必须以撇号(’ )开头,其名称通常全是小写,类似于泛型其名称非常短。大多数人使用 ’a 作为第一个生命周期注解。生命周期参数注解位于引用的 & 之后,并有一个空格来将引用类型与生命周期注解分隔开。
helle 变量的引用:&hello
helle 生命周期为 ’a 变量的引用 :&'a hello
helle 生命周期为 ’a 变量的可变引用 &'a mut hello
#![allow(unused)] fn main() { // 表明返回值与两个参数存活的一样久,当具体的引用被传递给 longest 时,被 'a 所替代的具体生命周期是 x 的作用域与 y 的作用域相重叠的那一部分。换一种说法就是泛型生命周期 'a 的具体生命周期等同于 x 和 y 的生命周期中较小的那一个。因为我们用相同的生命周期参数 'a 标注了返回的引用值,所以返回的引用值就能保证在 x 和 y 中较短的那个生命周期结束之前保持有效。 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() >= y.len() { x } else { y } } }
当从函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配。如果 返回的引用 没有 指向任何一个参数,那么唯一的可能就是它指向一个函数内部创建的值。然 而它将会是一个悬垂引用,因为它将会在函数结束时离开作用域。尝试考虑这个并不能编译的 longest 函数实现:
#![allow(unused)] fn main() { fn longest<'a>(x: &'a str, y: &str) -> &'a str { let result = String::from("hello"); &result // 不能返回函数内变量 } }
结构体定义中的生命周期注解
结构体也可以定义包含引用的字段,不过需要为每一个引用添加生命周期注解。
struct ImportantExcerpt<'a> { part: &'a str, } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().unwrap(); let i = ImportantExcerpt{ part: first_sentence, }; println!("{}", i.part); }
生命周期省略规则
函数或方法的参数的生命周期被称为 输入生命周期(input lifetimes),而返回值的生命周期被称为 输出生命周期(output lifetimes)。
规则一:编译器为每个生命周期分配一个生命周期参数。函数有一个引用就有一个生命周期参数 fn foo<'a>(x: &'a str) 、 fn foo<'a>(x: &'a str, y: &'b str)。
规则二:如果只有一个输入生命周期参数那么它被赋予所有输出生命周期参数。fn foo<'a>(x: &'a str) -> (first: &'a str, last: &'a str)。
规则三:如果有多个生命周期参数并且其中有一个是 &self 和 &mut self 说明是个对象的方法。那么所有输出生命周期参数被赋予 self 的生命周期 fn part(&'a self, x: &'b str) -> &'a str 。
静态生命周期
'static 生命周期存活于整个程序期间。所有字符串字面值都拥有 'static 生命周期。let s: &'static str = "hello"; 可以标注出来。