1. 什么是生命周期
生命周期的界说
在Rust中,每一个引证都有一个生命周期,即这个引证所指向的值在内存中存在的时间段。生命周期用来保证引证在其整个生命周期内都是有用的。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
在上面的代码中,函数longest
有两个输入参数,它们都是字符串切片的引证。这两个引证都有一个生命周期参数'a
,表明它们有必要具有相同的生命周期。函数返回值也有一个生命周期参数'a
,表明返回值的生命周期与输入参数的生命周期相同。
生命周期和内存办理
Rust经过生命周期来办理内存。当一个变量脱离它的效果域时,它占用的内存就会被开释。假如一个引证指向了一个已经被开释的内存空间,那么这个引证就变成了悬垂引证,运用它会导致未界说行为。
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("r: {}", r);
}
在上面的代码中,变量x
在脱离它的效果域后被开释,可是变量r
依然保留了对它的引证。这样就产生了一个悬垂引证。Rust编译器会查看这种状况,并给出过错提示。
2. 为什么需求生命周期
防止悬垂引证
正如上面提到的,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
返回了一个字符串切片的引证。编译器会查看这个返回值的生命周期是否合法。假如返回值是悬垂引证,编译器会给出过错提示。
保证内存安全
Rust经过生命周期来保证内存安全。编译器会查看代码中所有引证是否满意借用规矩。假如不满意,编译器会给出过错提示。
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &mut s;
println!("{}, {}, and {}", r1, r2, r3);
}
在上面的代码中,变量s
一起存在可变引证和不可变引证。这违反了Rust的借用规矩。编译器会查看这种状况,并给出过错提示。
3. 生命周期的语法
生命周期标示
在函数界说中,能够运用尖括号来标示生命周期参数。生命周期参数的名称有必要以撇号最初,例如'a
。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
在上面的代码中,函数longest
有两个输入参数,它们都是字符串切片的引证。这两个引证都有一个生命周期参数'a
,表明它们有必要具有相同的生命周期。函数返回值也有一个生命周期参数'a
,表明返回值的生命周期与输入参数的生命周期相同。
生命周期省掉规矩
在许多状况下,Rust编译器能够主动推断出引证的生命周期。这种状况下,能够省掉生命周期标示。
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
在上面的代码中,函数longest
没有显式标示生命周期参数。编译器会依据函数的界说主动推断出正确的生命周期。
在许多状况下,Rust编译器能够主动推断出引证的生命周期。这种状况下,能够省掉生命周期标示。Rust编译器运用一套称为生命周期省掉规矩的规矩来主动推断出正确的生命周期。
生命周期省掉规矩分为三条:
- 每一个引证参数都获得它自己的生命周期参数。例如,函数
fn foo(x: &i32)
会被转换为fn foo<'a>(x: &'a i32)
。 - 假如函数只要一个输入生命周期参数,那么它被赋予所有输出生命周期参数。例如,函数
fn foo<'a>(x: &'a i32) -> &i32
会被转换为fn foo<'a>(x: &'a i32) -> &'a i32
。 - 假如函数有多个输入生命周期参数,但其间一个是
&self
或&mut self
,那么它被赋予所有输出生命周期参数。例如,方法fn foo(&self, x: &i32) -> &i32
会被转换为fn foo<'a, 'b>(&'a self, x: &'b i32) -> &'a i32
。
这些规矩使得Rust编译器能够在许多状况下主动推断出正确的生命周期。可是,在一些复杂的状况下,编译器依然无法主动推断出正确的生命周期,需求程序员显式标示。
4. 生命周期的运用场景
函数参数和返回值
当函数的输入参数或返回值包括引证时,需求运用生命周期来保证引证的有用性。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
在上面的代码中,函数longest
有两个输入参数,它们都是字符串切片的引证。这两个引证都有一个生命周期参数'a
,表明它们有必要具有相同的生命周期。函数返回值也有一个生命周期参数'a
,表明返回值的生命周期与输入参数的生命周期相同。
结构体界说
当结构体中包括引证时,需求运用生命周期来保证引证的有用性。
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
let i = ImportantExcerpt { part: first_sentence };
}
在上面的代码中,结构体ImportantExcerpt
包括一个字符串切片的引证。这个引证有一个生命周期参数'a
,表明它有必要具有一个确认的生命周期。
5. 生命周期的高档用法
生命周期子类型和多态
Rust支持生命周期子类型和多态。生命周期子类型指的是一个生命周期能够包括另一个生命周期。生命周期多态指的是一个函数或类型能够适用于多个不同的生命周期。
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
}
在上面的代码中,函数longest
的第一个输入参数有一个生命周期参数'a
,而第二个输入参数没有生命周期参数。这表明第二个输入参数的生命周期可所以恣意的,它不会影响函数的返回值。
静态生命周期
Rust中有一个特殊的生命周期'static
,表明引证的值在整个程序运行期间都是有用的。
let s: &'static str = "I have a static lifetime.";
在上面的代码中,变量s
是一个字符串切片的引证,它具有静态生命周期。这表明它在整个程序运行期间都是有用的。
6. 生命周期和借用查看器
借用查看器的效果
Rust编译器中包括一个借用查看器,它担任查看代码中所有引证是否满意借用规矩。假如不满意,编译器会给出过错提示。
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &mut s;
println!("{}, {}, and {}", r1, r2, r3);
}
在上面的代码中,变量s
一起存在可变引证和不可变引证。这违反了Rust的借用规矩。编译器会查看这种状况,并给出过错提示。
生命周期和借用规矩
Rust经过生命周期来保证代码中所有引证都满意借用规矩。编译器会查看代码中所有引证的生命周期,保证它们在整个生命周期内都是有用的。
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
x
}
在上面的代码中,函数longest
返回了一个字符串切片的引证。编译器会查看这个返回值是否满意借用规矩。假如不满意,编译器会给出过错提示。
7. 生命周期的局限性和未来发展
生命周期的局限性
尽管Rust经过生命周期来办理内存和保证内存安全,可是生命周期也有一些局限性。例如,有时候编译器无法主动推断出正确的生命周期,需求程序员显式标示。这会增加程序员的负担,并下降代码可读性。
未来发展方向
Rust言语团队一直在尽力改善生命周期体系,让它愈加智能和易用。例如,在Rust 2018版别中,引入了一种新的生命周期省掉规矩,能够让编译器主动推断出更多状况下的正确生命周期。
总之,Rust经过生命周期来办理内存和保证内存安全。尽管生命周期有一些局限性,可是Rust言语团队一直在尽力改善它,让它愈加智能和易用。from刘金,转载请注明原文链接。感谢!