首页 技术杂谈 正文
  • 本文约2085字,阅读需10分钟
  • 286
  • 0

Rust - 生命周期

摘要

Rust生命周期,确保引用有效性的关键机制。通过明确的生命周期注解,Rust防止了悬垂指针和数据竞争,保障了内存安全。生命周期是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生命周期要三思:

  • 这个引用是否在程序整个生命周期都存活
评论