生命周期
生命周期主要就是为了保证正确引用, 确保不会出现悬垂引用(dangling references)
rust
fn main() {
let mut a = &10;
{
let b = 20;
a = &b;
// 编译器报错: b dose not live long enough(变量 b 存活的时间不够长)
// a 的生命周期是整个 main 函数体, 而 b 的生命周期却只有
// 这个大括号这么长, 当代码执行到这个大括号后面时, 里面的
// 变量就被销毁了(内存被释放), 所以编译器就会报错
// 编译器能够自动检测出来的, 可以比较两个变量的声明周期长度
// 如果需要这个代码通过编译, 只需要让他们的生命周期一样长即可
}
println!("a = {}", a);
}
rust
fn main() {
let mut a = &10;
let b = 20;
a = &b;
// 此时他们的生命周期是一样长的, 所以不会报错
println!("a = {}", a);
}
函数中生命周期标注
- 类似泛型, 在函数名后的 <'x> 中标注
rust
// 此时编译器会报错: missing lifetime specifier
// 因为函数的参数作为返回值返回了(参数可以看做函数的局部变量)
// 但是并没有标注生命周期, 那么当函数执行完之后函数的所有参数
// 都应该被销毁掉, 那么就相当于返回一个被销毁的变量的引用,
// 这样就会导致悬垂引用(空指针)
fn longset(s1: &str, s2: &str) -> &str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
rust
// 's 表示: 告诉编译器我要将参数当作返回值, 在函数执行完时,
// 不要自动销毁这个变量, 传入的引用什么时候销毁, 你就什么时候销毁
fn longset<'s>(s1: &'s str, s2: &'s str) -> &'s str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
fn main() {
let s1 = "abc";
let s2 = "abcd";
println!("{}", longset(s1, s2)); // abcd
}
声明周期注解语法
生命周期标注不会改变引用的生命周期长度
当指定了泛型生命周期参数, 函数可以接受带有任何生命周期的引用
生命周期的标注: 描述了描述了多个引用的生命周期的关系, 但并不影响生命周期
生命周期参数名:
- 以
'
开头 - 全部小写且非常短
- 常见的使用
'a
, 但是可以使用别的比如:'s
- 以
生命周期标注位置:
- 在引用的
&
符号后 - 在
mut
关键字前
- 在引用的
rust
&i32 // 一个引用
&'a i32 // 带有显示生命周期标注的引用
&'a mut i32 // 带有显示生命周期标注的可变引用
函数签名中的生命周期标注
's
会获取所有使用这个标注的参数(s1
s2
)的生命周期- 如果两个变量的生命周期不一样长, 那么取生命周期较短的那个作为标准
rust
fn longset<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
// s1 和 s2 生命周期是一样的
// 那么此时的 'a 生命周期标注代表的是
// 整个 main 函数体 {} 的范围
fn main() {
let s1 = "abc";
let s2 = "abcd";
println!("{}", longset(s1, s2)); // abcd
}
rust
fn longset<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
// 此时的 'a 生命周期标注代表的是, 当前的这个 {} 的范围
// 因为 s2 比 s1 生命周期要短, 如果两个变量的生命周期不一样长,
// 那么取生命周期较短的那个作为标准
fn main() {
let s1 = String::from("hello");
{ // 生命周期标注代表的范围
let s2 = String::from("s2-hello");
let res = longset(&s1, &s2);
println!("longest:{}", res);
} // 到此处就结束
}
rust
fn longset<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}
fn main() {
let s1 = String::from("hello");
let mut res: &str;
{
let s2 = String::from("s2-hello");
res = longset(&s1, &s2);
}
// 此时已经超出了生命周期标注代表的作用域范围, 因为
// s1 和 res 的生命周期都是整个main函数, 而 s2 的生命周期只有 {}
// 因为返回值也是标注了 'a, 所以它也是: 取生命周期较短的那个作为标准
// 所以此时的 res 生命周期已经结束了, 再去获取就会报错
println!("longest:{}", res);
}
深入了解生命周期
- 指定生命周期的真缺方式是由函数具体实现的功能决定的
rust
fn longset<'a>(s1: &'a str, s2: &str) -> &'a str {
s1
}
// 虽然传入了2个参数, 但是没有用到 s2,
// 所以s2也就不需要标注生命周期了
// 那么 'a 代表的就是 s1 的生命周期
// 所以这段代码就能够通过编译
fn main() {
let s1 = String::from("hello");
let mut res: &str;
{
let s2 = String::from("s2-hello");
res = longset(&s1, &s2);
}
println!("longest:{}", res);
}
结构体定义中的生命周期标注
之前我们自定义过有数据所有权
类型的结构体, 其实也可以定义 包含引用
的结构体
rust
struct User {
id: u32,
email: String,
}
// 之前没有定义过这种
struct Student<'a> {
id: u32,
name: &'a str
}
fn main() {
let u = User {
id: 1001,
email: String::from("example@example.com"),
};
println!("email: {}", u.email);
let s = Student {
id: 1002,
name: "John Doe",
};
println!("name: {}", s.name);
}
生命周期的省略规则
- 每个引用都有生命周期
- 需要为使用生命周期的函数/struct标注生命周期参数
rust
// 按照道理来说, 这个代码必须要标注生命周期才能通过编译
// 传入的是 &str 是一个引用, 返回的 &str 也是一个引用
// 但是, 现在不用手动的标注生命周期参数, 也能编译
// --------这是因为,所有的引用都手动标注未免太繁琐
// 所以 rust 编译器,会将一些符合特定规则的生命周期参数(包括参数/返回值)
// 直接写进了编译器中,当遇到这些规则时,会自动添加标注
// 符合这些规则的,就是: 生命周期的省略规则
// 但是如果代码不符合这些规则, 那么编译器无法自动添加标注, 就会报错
#![allow(unused)]
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[..]
}
输入/输出生命周期
- 输入生命周期: 函数/方法的参数
- 输出生命周期: 函数/方法的返回值
生命周期的省略规则
- 每个引用类型的参数都有自己的生命周期(只能用于 输入生命周期)
- 如果只有一个输入生命周期, 那这个唯一的生命周期赋给所有的输出生命周期(如: first_word 例子)
- 如果有多个输入生命周期参数, 但是其中一个是
&self
或者&mut self
, 那这个 &slef 的生命周期就会赋给所有的输出生命周期
静态生命周期
'static
是一个特殊的生命周期, 它表示整个程序的的持续时间
rust
fn main() {
// 字符串切片就是一个静态的生命周期
// 因为: 字符串切面是直接编译在二进制可执行程序中的
let msg: &'static str = "hello";
}
谨慎使用静态生命周期, 因为并不是所有的引用都需要在程序运行时都存活的