【Rust生命周期】一文搞懂Rust语言生命周期机制

06-02 1494阅读

【Rust生命周期】一文搞懂Rust语言生命周期机制

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑

🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。

🏆《博客》:Rust开发,Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Rust语言通关之路

景天的主页:景天科技苑

【Rust生命周期】一文搞懂Rust语言生命周期机制

文章目录

  • Rust生命周期
    • 一、生命周期基础
      • 1.1 什么是生命周期
      • 1.2 为什么需要生命周期
      • 1.3 生命周期注解语法
      • 二、函数中的生命周期
        • 2.1 函数签名中的生命周期
        • 2.2 实际应用示例
        • 2.3 生命周期省略规则
        • 三、结构体中的生命周期
          • 3.1 结构体定义中的生命周期
          • 3.2 实际案例:文本处理
          • 四、方法定义中的生命周期
            • 4.1 impl块中的生命周期
            • 4.2 生命周期与self的关系
            • 五、静态生命周期
              • 5.1 'static生命周期
              • 5.2 使用场景与注意事项
              • 六、生命周期与泛型结合
                • 6.1 泛型类型参数、trait约束和生命周期
                • 6.2 实际案例:缓存系统
                • 七、高级生命周期模式
                  • 7.1 生命周期子类型
                  • 八、常见生命周期错误与解决方法
                    • 8.1 错误示例1:返回局部变量的引用
                    • 8.2 错误示例2:结构体生命周期不匹配
                    • 8.3 错误示例3:迭代器与生命周期的交互
                    • 九、总结

                      Rust生命周期

                      Rust语言以其内存安全和并发安全著称,而生命周期(Lifetimes)是实现这些安全特性的核心机制之一。生命周期是Rust中最具特色但也最令初学者困惑的概念之一。

                      一、生命周期基础

                      1.1 什么是生命周期

                      生命周期是Rust中用来确保引用始终有效的范围标记。它告诉编译器引用的有效作用域,从而防止悬垂引用(Dangling Reference)——即引用指向已经被释放的内存。

                      在Rust中,每个引用都有其生命周期,尽管大多数情况下编译器可以自动推断而不需要我们显式标注。

                      Rust 中的每一个引用都有其 生命周期(lifetime),也就是引用保持有效的作用域。大部分时候生命周期是隐含并可以推断的,正如大部分时候类型也是可以推断的一样。

                      类似于当因为有多种可能类型的时候必须注明类型,也会出现引用的生命周期以一些不同方式相关联的情况,

                      所以 Rust 需要我们使用泛型生命周期参数来注明他们的关系,这样就能确保运行时实际使用的引用绝对是有效的。

                      1.2 为什么需要生命周期

                      Rust 不使用垃圾回收,而是依赖所有权系统来管理内存。

                      为了保证在编译期就能检测出潜在的内存错误,Rust 要知道每个引用在内存中的作用域(scope)有多长,这就是生命周期的本质:一种描述引用有效范围的机制。

                      生命周期避免了悬垂引用

                      考虑以下代码:

                      fn main() {
                          let r;
                          {
                              let x = 5;
                              r = &x;
                          }
                          println!("r: {}", r);
                      }
                      

                      这段代码无法编译,因为内部块中的x在块结束时被丢弃,而r试图在块外引用它。Rust的生命周期系统会捕获这种错误。

                      【Rust生命周期】一文搞懂Rust语言生命周期机制

                      那么 Rust 是如何决定这段代码是不被允许的呢?

                      借用检查器

                      编译器的这一部分叫做 借用检查器(borrow checker),它比较作用域来确保所有的借用都是有效的。

                      【Rust生命周期】一文搞懂Rust语言生命周期机制

                      我们将 r 的生命周期标记为 'a 并将 x 的生命周期标记为 'b 。如你所见,内部的 'b 块要比外部的生命周期 'a 小得多。

                      在编译时,Rust 比较这两个生命周期的大小,并发现 r 拥有生命周期 'a ,不过它引用了一个拥有生命周期’b 的对象。

                      程序被拒绝编译,因为生命周期 'b 比生命周期 'a 要小:被引用的对象比它的引用者存在的时间更短。

                      让我们看看示例 10-20 中这个并没有产生悬垂引用且可以正确编译的例子:

                      【Rust生命周期】一文搞懂Rust语言生命周期机制

                      这里 x 拥有生命周期 'b ,比 'a 要大。这就意味着 r 可以引用 x :Rust 知道 r 中的引用在 x 有效的时候也总是有效的。

                      现在我们已经在一个具体的例子中展示了引用的生命周期位于何处,并讨论了 Rust 如何分析生命周期来保证引用总是有效的

                      rust的生命周期始终是和借用、引用相关的

                      1.3 生命周期注解语法

                      生命周期注解并不改变任何引用的生命周期的长短。与当函数签名中指定了泛型类型参数后就可以接受任何类型一样,

                      当指定了泛型生命周期后函数也能接受任何生命周期的引用。生命周期注解所做的就是将多个引用的生命周期联系起来。

                      生命周期参数以撇号’开头,通常是小写字母,如’a。

                      生命周期参数注解位于引用的 & 之后,并有一个空格来将引用类型与生命周期注解分隔开。

                      &i32        // 普通引用
                      &'a i32     // 带有显式生命周期的引用
                      &'a mut i32 // 带有显式生命周期的可变引用
                      

                      二、函数中的生命周期

                      2.1 函数签名中的生命周期

                      就像泛型类型参数,泛型生命周期参数需要声明在函数名和参数列表间的尖括号中。fn funcname(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }

                      这里的’a是一个生命周期参数,表示x和y必须至少活得像’a一样长,且返回值也至少活得像’a一样长。

                      当在函数中使用生命周期注解时,这些注解出现在函数签名中,而不存在于函数体中的任何代码中。

                      这是因为 Rust 能够分析函数中代码而不需要任何协助,不过当函数引用或被函数之外的代码引用时,参数或返回值的生命周期可能在每次函数被调用时都不同。

                      这可能会产生惊人的消耗并且对于 Rust 来说通常是不可能分析的。在这种情况下,我们需要自己标注生命周期。

                      如果将函数中创建局部变量的引用返回,即便标注了生命周期,借用检查也会报错,因为函数内创建的局部变量,在离开函数作用域后,就会调用drop函数,将变量销毁

                      此时再返回该引用,会出现悬垂引用

                      【Rust生命周期】一文搞懂Rust语言生命周期机制

                      出现的问题是 s 在 longest 函数的结尾将离开作用域并被清理,而我们尝试从函数返回一个 s 的引用。

                      无法指定生命周期参数来改变悬垂引用,而且 Rust 也不允许我们创建一个悬垂引用。

                      在这种情况,最好的解决方案是返回一个有所有权的数据类型而不是一个引用,这样函数调用者就需要负责清理这个值了。

                      从结果上看,生命周期语法是关于如何联系函数不同参数和返回值的生命周期的。

                      一旦他们形成了某种联系,Rust 就有了足够的信息来允许内存安全的操作并阻止会产生悬垂指针亦或是违反内存安全的行为。

                      2.2 实际应用示例

                      fn longest
                          if x.len()  y.len() { x } else { y }
                      }
                      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);
                          }
                      }
                      

                      【Rust生命周期】一文搞懂Rust语言生命周期机制

                      这个例子可以正常工作,因为string2的生命周期足够长。但如果尝试在string2离开作用域后使用result,编译器会报错。

                      【Rust生命周期】一文搞懂Rust语言生命周期机制

                      2.3 生命周期省略规则

                      早期在Rust中,必须显示地声明生命周期,后来将很明确的模式进行了注解的简化。

                      Rust团队发现某些生命周期模式非常常见,因此编译器可以自动推断这些模式而不需要显式注解。

                      遵守生命周期省略规则的情况下能明确变量的声明周期,则无需明确指定生命周期。函数或者方法的参数的生命周期称为输入生命周期,而返回值的生命周期称为输出生命周期。

                      编译器采用三条规则判断引用何时不需要生命周期注解,当编译器检查完这三条规则后仍然不能计算出引用的生命周期,则会停止并生成错误。

                      这些规则称为"生命周期省略规则":

                      1)每个引用参数都有自己的生命周期参数,换句话说就是,有一个引用参数的函数有一个生命周期参数,有两个引用参数的函数有两个不同的生命周期参数

                      fn foo(x: &i32) → fn foo &i32 → fn foo &'a i32
                      

                      3)方法中如果有多个输入生命周期参数,但其中一个是&self或&mut self,则self的生命周期被赋予所有输出生命周期参数。这使得方法编写起来更简洁。

                      三、结构体中的生命周期

                      3.1 结构体定义中的生命周期

                      目前为止,我们只定义过有所有权类型的结构体。也可以定义存放引用的结构体,不过需要为结构体定义中的每一个引用添加生命周期注解

                      当结构体包含引用时,必须在结构体定义中声明生命周期:

                      //结构体中的生命周期
                      // 结构体中包含一个引用
                      // 结构体的生命周期必须与引用的生命周期相同
                      //也是以尖括号的形式来表示生命周期
                      struct ImportantExcerpt
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码