Rust - 生命周期
生命周期
-
Rust的每个引用都有自己的生命周期
-
生命周期:引用保持有效的作用域
-
大多数情况:生命周期是隐式的、可被推断的
-
当引用的生命周期可能以不同的方式互相关联时:手动标注生命周期
-
生命周期 - 避免悬垂引用
- 生命周期的主要目标:避免悬垂引用
fn main() {
let x;
{
// y 的生命周期只有在这个作用域内有效,所以程序报错
let y =10;
x = &y;
}
println!("{}",x);
}
1、函数中的泛型生命周期
fn main() {
let s1 = String::from("hello");
let s2 = "world";
let result = long_str(s1.as_str(),s2);
}
// 简单比较字符串字面值大小
// 要添加泛型生命周期才不会报错
fn long_str<'a>(x:&'a str,y:&'a str) -> &'a str {
if x.len()>y.len(){
x
}else {
y
}
}
2、生命周期的语法标注
- 生命周期的标注不会改变引用的生命周期长度
- 当指定了泛型生命周期参数,函数可以接收任何带有生命周期的引用
- 生命周期的标注:描述了多个引用的生命周期间的关系,但不影响生命周期
语法
1、生命周期参数名:
- 以'开头
- 通常全小写且非常短
- 很多人使用'a
2、生命周期标注的位置:
- 在引用的&符号后
- 使用空格将标注和引用类型分开
例子:
- &i32 : 一个引用
- &'a i32 : 带有显示生命周期的引用
- &'a mut i32 : 带有显示生命周期的可变引用
结论: 根据上面比较字符串的例子,说明单个生命周期标注没有意义,本来的作用就是配合泛型来定义参数存活的时间,实际上在Rust里,是判断这两个参数的类型,取其中生命周期最短的一个来使用。
fn main() {
let s1 = String::from("hello");
let resutl;
{
// 这里还是会报错,因为之前str类型作用域是静态全局作用域
// 这里的s2是只在括号里,因为只取两个中最短的原则,所以就会报错
// let s2 = "world"; 改成字符串
let s2 = String::from("world");
let result = long_str(s1.as_str(),s2.as_str());
}
println!("{}",result);
}
// 简单比较字符串字面值大小
// 要添加泛型生命周期才不会报错
fn long_str<'a>(x:&'a str,y:&'a str) -> &'a str {
if x.len()>y.len(){
x
}else {
y
}
}
3、深入理解生命周期
1、指定生命周期参数的方式依赖于函数所做的事情
2、从函数返回引用时,返回类型的生命周期参数需要与其中一个参数的生命周期匹配
3、如果返回的引用没有指向任何参数,那么它只能引用函数内创建的值
- 这就是悬垂引用:该值在函数结束时就走出了作用域
4、总结:生命周期语法就是服务于函数的参数和返回值的生命周期的
fn main() {
let s1 = String::from("hello");
let s2 = "world";
let result = long_str(s1.as_str(),s2);
println!("{}",result);
}
// 悬垂引用
// 修改 把返回值的类型 从&'a str 改成String
fn long_str<'a>(x:&'a str,y:&'a str) -> &'a str {
// 这里的result在括号结束后内存就清理了,所以后面在main中还打印了该值,所以就会报错
let result = String::from("abc");
result.as_str()
}
4、Struct中定义生命周期标注
struct ImportantExcerpt<'a>{
part:&'a str,
}
fn main() {
//Struct的生命周期
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.')
.next()
.expect("Counld not found a '.'");
let i = ImportantExcerpt{
part: first_sentence
};
}
5、生命周期的省略
1、都知道
- 每个引用都有生命周期
- 需要为使用生命周期的函数或struct指定生命周期参数
2、但是在现在的编译器中,开发者把一些常用的会出现生命周期标注的地方,都写到编译器中,所以现在有些函数的编写就可以不用写生命周期标注,因为编译器会自动推断
3、在Rust引用分析中所编入的模式称为生命周期省略规则
- 这些规则无需开发者遵守
- 它们是一些特使情况,由编译器考虑
- 如果你的代码符合这些情况,那么就无需显示标注生命周期
4、生命周期省略规则不会提供完整的推断
- 如果应用规则后,引用的生命周期任然模糊不清->会导致编译错误
- 解决方法:手动的添加生命周期,表明引用间的相互关系
6、输入、输出生命周期
1、生命周期在:
- 函数/方法的参数:称为输入生命周期
- 函数/方法的返回值:称为输出生命周期
7、生命周期省略的三个规则
1、编译器使用3个规则在没有显示标注生命周期的情况下,来确定引用的生命周期
- 规则1 应用于输入生命周期
- 规则2,3应用于输出生命周期
- 如果编译器应用完3个规则之后,任然有无法确定生命周期的引用->直接报错
- 这些规则适用于fn定义和impl块
2、规则1:每个引用类型的参数都有自己的生命周期
3、规则2:如果只有1个输入生命周期参数,那么该生命周期被赋给所有的输出生命周期参数
4、规则3:如果有多个输入生命周期参数,但其中一个是&self或&mut self(方法中),那么self的生命周期会被赋给所有的输出生命周期参数
5、例子
- fn test(&str)->&str{} : 那么编译器会先分析符合规则1不,这里符合,这时候代码就变成
- fn test<'a>(&'a str) ->&str{}: 这是时候返回值还没有生命周期,编译器会继续判断是否符合第二种规则,这里符合,代码变成
- fn test<'a>(&'a str)->&'a str{}
6、例子二
- fn test(&str,&str) -> &str{}:这里先符合规则1,代码变成
- fn test<'a>(&'a str, &'a str) -> &str:但是这里由于返回值没有指定生命周期,并且现在情况并不符合规则2和3,所以代码会报错
8、方法定义中的生命周期标注
1、在struct上使用生命周期实现方法,语法和泛型参数的语法一样
2、在哪声明和使用生命周期参数,依赖于:
- 生命周期参数时候和字段、方法的参数或返回值有关
3、struct字段的生命周期名:
- 在impl后生命
- 在struct名后使用
- 这些生命周期是struct类型的一部分
4、impl块内的方法签名中:
- 引用必须绑定于struct字段引用的生命周期,或者引用是独立的也可以
- 生命周期省略规则经常使得方法中的生命周期标注不是必须的
struct Import<'a>{
part:&'a str,
}
// 定义方法
impl<'a> Import<'a> {
// 根据规则1,可以自动推断出参数和返回值的生命周期
fn level(&self) -> i32{
3
}
// 根据规则3 也可以自动推断出生命周期
fn announce_and_return_part(&self,announce:&str) -> &str{
print!("{}",announce);
self.part
}
}
fn main() {
}
9、静态生命周期
1、'staic 是一个特殊的生命周期:整个程序的持续时间。
- 例如:所有的字符串字面值都拥有'staic生命周期
- let s:'staic str = "hello world";
2、为引用指定'staic生命周期要三思:
- 这个引用是否在程序整个生命周期都存活
推荐阅读: