生命周期
什么是生命周期
Rust 每个引用都有自己的生命周期。
生命周期:引用保持有效的作用域。
大多数情况:生命周期是隐式的、可被推断的。
当引用的生命周期可能以不同的方式互相关联时:手动标注生命周期。
生命周期的作用:
- 主要作用就是为了避免悬垂引用(dangling reference)。
/* error[E0597]: `x` does not live long enough --> src/main.rs:6:17 | 6 | r = &x; | ^^ borrowed value does not live long enough 7 | } | - `x` dropped here while still borrowed 8 | 9 | println!("r : {}", r); | - borrow later used here */ let r; { let x = 5; r = &x; } println!("r : {}", r);
借用检查器:
- 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 其中之一,x、y 的具体生命周期也无法确定 // 而且与函数的实现体无关,rust 只检查函数签名 // 需要手动增加生命周期。 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
生命周期的标注:
- 生命周期的标注不会改变引用的生命周期长度。
- 当指定了泛型生命周期参数,函数可以接收带有
任何
生命周期的引用。 - 生命周期的标注:描述了多个引用的生命周期间的关系,但不影响生命周期。
生命周期的标注语法:
- 生命周期参数名:
- 以
'
开头。 - 通常全小写且非常短。
- 通常使用
'a
。
- 以
- 生命周期标注的位置:
- 在引用的 ‘&’ 符号后。
- 使用空格将标注和引用类型分开。
- 生命周期参数名:
函数签名中的生命周期标注:
- 泛型生命周期参数声明在:函数名和参数列表之间的
<>
里。
- 泛型生命周期参数声明在:函数名和参数列表之间的
生命周期的约束如何生效:
- 当 string1、string2 的生命周期不在一致,且返回值 result 生命周期长度大于 string2 时,编译报错。
- 生命周期
'a
的实际生命周期是:x 和 y 两个生命周期中较小的哪个。
/* error[E0597]: `string2` does not live long enough --> src/main.rs:7:44 | 7 | result = longest(string1.as_str(), string2.as_str()); | ^^^^^^^^^^^^^^^^ borrowed value does not live long e nough 8 | } | - `string2` dropped here while still borrowed 9 | println!("The longest string is {}", result); | ------ borrow later used here */ fn main() { let result; let string1 = String::from("abcd"); { let string2 = String::from("xyz"); //这里改成 String 是由于 静态字符串非堆内存 j result = longest(string1.as_str(), string2.as_str()); } 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 } }
深入理解生命周期
指定生命周期参数的方式依赖于函数所做的事情。
- 如下例中,返回的只有 x,那么参数 y 就不再需要生命周期的标注,只需要保证 x 与返回值 str
切片的生命周期一致即可。
fn longest<'a>(x: &'a str, y: &str) -> &'a str { x }
- 如下例中,返回的只有 x,那么参数 y 就不再需要生命周期的标注,只需要保证 x 与返回值 str
当函数返回引用时,返回类型的生命周期参数需要与其中一个参数的生命周期匹配。
如果返回的引用没有指向任何参数,那么它就只能引用函数内创建的值:
- 这就是悬垂引用:该值在函数结束时就走出里作用域。
Struct 定义中的生命周期标注
Struct 里可包括:
- 自持有的类型。
- 引用:需要在每个引用上添加生命周期标注。
// part 所引用的对象 str 的生命周期需要包括整个 struct 实例的生命周期。 struct ImportantExcerpt<'a> { part: &'a str, } fn test_struct_lifttime() { let novel = String::from("Call me Ishmael. Some years ago ..."); let first_sentence = novel.split('.').next().expect("Could not found a ."); let i = ImportantExcerpt { part: first_sentence, }; println!("print part {}", i.part); }
生命周期的省略
经过前文我们了解到,每个引用都有生命周期,需要为使用生命周期的函数或 struct 指定生命周期参数。
- 但是我们之前在 02 节所有权中又一个例子返回了引用却没有标注生命周期,这就是生命周期的省略。
fn first_world_v2(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[..i]; } } &s[..] }
生命周期的省略规则:在 Rust 引用分析中所编入的模式称为
生命周期省略规则
。- 这些规则无需开发者来遵守。
- 它们是一些特殊情况,由编译器来考虑。
- 如果你的代码符合这些情况,那么就无需显式的标注生命周期。
生命周期省略规则不会提供完整的推断:
- 如果应用规则后,引用的生命周期仍然模糊不清,这是则会编译错误。
- 解决方法:添加生命周期标注,表明引用间的相互关系。
输入生命周期与输入生命周期
- 生命周期在:
- 函数/方法的参数:输入生命周期。
- 函数/方法的返回值:输出生命周期。
生命周期省略的三个规则
- 规则 1 应用于输出生命周期。
- 规则 2、3 应用于输入生命周期。
- 这些规则适用于 fn 定义和 impl 块。
- 规则 1:每个引用类型的参数都有自己的生命周期。
- 规则 2:如果只有 1 个输入生命周期参数,那么该生命周期被赋给所有的输出生命周期参数。
- 规则 3:如果有多个生命周期参数,但其中一个是
&self
或&mut self
(是方法),那么 self
的生命周期会被赋给所有的输出生命周期参数。
方法定义中的生命周期标注
在 struct 上使用生命周期实现方法,语法和泛型参数的语法一样。
在哪生命和使用生命周期参数,依赖于:
- 生命周期参数是否和字段、方法的参数或返回值有关。
struct 字段的生命周期名:
- 在
impl
后声明。 - 在
struct
名子后使用。 - 这些生命周期是 struct 类型的一部分。
- 在
impl 块内的方法签名中:
引用
必须绑定于 struct 字段引用的生命周期,或者引用是独立的也可以。- 生命周期省略规则经常使得方法中的生命周期标注不是必须的。
struct ImportantExcerpt<'a> { part: &'a str, } impl<'a> ImportantExcerpt<'a> { fn level(&self) -> i32 { 3 } // 返回的引用被赋予来 self 的生命周期 fn announce_and_return_part(&self, announcement: &str) -> &str { println!("Attention please: {}", announcement); self.part } }
静态生命周期
'static
是一个特殊的生命周期:整个程序的持续时间。- 例如:所有的字符串字面值都拥有
'static
生命周期。let s: &'static str = "I have a static lifetime";
- 例如:所有的字符串字面值都拥有
为引用指定
'static
生命周期前要三思:- 是否需要引用在程序整个生命周期内都存活。
例子
fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where
T: std::fmt::Display,
{
println!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}