生命周期
通过使用生命周期,您可以指定引用有效的范围,并且 Rust 编译器将确保不会在该范围之外使用该引用。这有助于防止常见的内存错误,例如空引用或悬垂引用,并允许您编写安全高效的代码。
生命周期注解不会改变任何引用的存活时间。相反,它们描述了多个引用之间的生命周期关系,而不影响这些生命周期。就像函数在签名指定泛型类型参数时可以接受任何类型一样,函数也可以通过指定泛型生命周期参数来接受具有任意生命周期的引用。
fn add<'a>(a: &'a i32, b: &'a i32) -> &'a i32 {
&(a + b)
}
函数生命周期
函数生命周期标注:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
'a
指代 x
与y
中生命周期较小的那个,而返回值str
的生命周期也等于x
与y
中生命周期较小的那个。
假设调用 fn longest
的场景是:
fn main() {
let string1 = String::from("long string is long");
{
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result);
}
}
上述场景中,string2
的生命周期为大括号,string1
的生命周期为整个main
函数。因此'a
指代string2
的生命周期。也就是说返回值result
的生命周期,也等同于srting2
,是大括号。
标注生命周期只是为了告诉编译器,因为longest
函数可能返回x
,也可能返回y
。在这种情况下,编译器无法使用借用检查器(Borrow checker)来检查程序的正确性。
延续以上的例子,在以下代码中就会产生编译错误,result 的生命周期只到大括号:
fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}
println!("The longest string is {}", result);
}
而分析代码可以发现,result
引用的是 string1
,而 string1
的生命周期是整个 main
函数。但 Rust 编译器比较保守,仍会选择报错。
方法生命周期
方法一般是不需要标明生命周期的,因为 self
的生命周期会赋给所有的输出生命周期参数。
结构体生命周期
#[derive(Debug)]
struct NamedBorrowed<'a> {
x: &'a i32,
y: &'a i32,
}
x 和 y 这两个引用都必须比结构体长寿。
生命周期约束
#[derive(Debug)]
struct Ref<'a, T: 'a>(
&'a T
);
Ref
包含一个指向泛型类型 T
的引用,其中 T
拥有一个未知的生命周期'a
。T
拥有生命周期限制,T
中的任何引用都必须比 'a
活得更长。另外 Ref
的生命周期也不能超出 'a
。
生命周期消除
当函数的返回值是从参数获取时,意味着参数的生命周期与返回值的一致。此时就无需标注生命周期。如:
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
生命周期消除规则:
- 每个引用参数都会获得独自的生命周期
- 若只有一个输入生命周期(函数参数中只有一个引用类型),那么该生命周期会被赋给所有的生命周期
- 若存在多个输入生命周期,且其中一个是
&self
(方法)或&mut self
,则&self
的生命周期被赋给所有的输出生命周期
静态生命周期
'static
,生命周期为整个程序的存活周期。
字符串字面量默认具有'static
生命周期:
let s: &'static str = "我没啥有点,就是活得久";