广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >rust生命周期详解
  • 266
分享到

rust生命周期详解

rust生命周期 2023-03-19 18:03:11 266人浏览 八月长安
摘要

目录rust生命周期借用检查函数中的生命周期手动声明生命周期结构体中的生命周期生命周期消除三条消除原则生命周期约束静态生命周期rust生命周期 生命周期是rust中用来规定引用的有效

rust生命周期

生命周期是rust中用来规定引用的有效作用域。在大多数时候,无需手动声明,因为编译器能够自动推导。当编译器无法自动推导出生命周期的时候,就需要我们手动标明生命周期。生命周期主要是为了避免悬垂引用

借用检查

rust的编译器会使用借用检查器来检查我们程序的借用正确性。例如:

#![allow(unused)]
fn main() {
{
    let r;

    {
        let x = 5;
        r = &x;
    }

    println!("r: {}", r);
}
}

在编译期,Rust 会比较两个变量的生命周期,结果发现 r 明明拥有生命周期 'a,但是却引用了一个小得多的生命周期 'b,在这种情况下,编译器会认为我们的程序存在风险,因此拒绝运行。

函数中的生命周期

#![allow(unused)]
fn main() {
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
}

执行这段代码,rust编译器会报错,它给出的help信息如下:

help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`

意思是函数返回类型是一个借用值,但是无法从函数的签名中得知返回值是从x还是y借用的。并且给出了相应的修复代码。

4 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
  |           ++++     ++          ++          ++

按照这个提示,我们更改函数声明。就会发现可以顺利通过编译。因此,像这样的函数,我们无法判断它是返回x还是y,那么只好手动进行生命周期声明。上面的提示就是手动声明声明周期的语法。

手动声明生命周期

需要注意的是,标记的生命周期只是为了取悦编译器,让编译器不要难为我们,它不会改变任何引用的实际作用域

生命周期的语法是以’开头,名称往往是一个单独的小写字母。大多数人用’a来作为生命周期的名称。如果是引用类型的参数,生命周期会位于&之后,并用空格来将生命周期和参数分隔开。函数签名中的生命周期标注和泛型一样,需要在提前声明生命周期。例如我们刚才修改过的函数签名

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str

该函数签名表明对于某些生命周期 'a,函数的两个参数都至少跟 'a 活得一样久,同时函数的返回引用也至少跟 'a 活得一样久。实际上,这意味着返回值的生命周期与参数生命周期中的较小值一致:虽然两个参数的生命周期都是标注了 'a,但是实际上这两个参数的真实生命周期可能是不一样的(生命周期 'a 不代表生命周期等于 'a,而是大于等于 'a)。例如:

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);
    }
}

result 的生命周期等于参数中生命周期最小的,因此要等于 string2 的生命周期,也就是说,result 要活得和 string2 一样久。如过我们将上面的代码改变为如下所示。

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is {}", result);
}

那么将会导致错误,因为编译器知道string2活不到最后一行打印。而string1可以活到打印,但是编译器并不知道longest返回的是谁。
函数的返回值如果是一个引用类型,那么它的生命周期只会来源于:

  • 函数参数的生命周期
  • 函数体中某个新建引用的生命周期

若是后者情况,就是典型的悬垂引用场景:

#![allow(unused)]
fn main() {
fn longest<'a>(x: &str, y: &str) -> &'a str {
    let result = String::from("really long string");
    result.as_str()
}
}

上面的函数的返回值就和参数 x,y 没有任何关系,而是引用了函数体内创建的字符串,而函数结束的时候会自动释放result的内存,从而导致悬垂指针。这种情况,最好的办法就是返回内部字符串的所有权,然后把字符串的所有权转移给调用者:

fn longest<'a>(_x: &str, _y: &str) -> String {
    String::from("really long string")
}

fn main() {
   let s = longest("not", "important");
}

结构体中的生命周期

在结构体中使用引用,只要为结构体中的每一个引用标注上生命周期即可。

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,
    };
}

part引用的first_sentence来自于novel,它的生命周期是main函数,因此这段代码可以正常工作。
ImportantExcerpt 结构体中有一个引用类型的字段 part,因此需要为它标注上生命周期。结构体的生命周期标注语法跟泛型参数语法很像,需要对生命周期参数进行声明 <'a>。该生命周期标注说明,结构体 ImportantExcerpt 所引用的字符串 str 必须比该结构体活得更久。

生命周期消除

编译器为了简化用户的使用,运用了生命周期消除大法。例如:

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 函数,它的返回值是一个引用类型,那么该引用只有两种情况:

  • 从参数获取
  • 从函数体内部新创建的变量获取

如果是后者,就会出现悬垂引用,最终被编译器拒绝,因此只剩一种情况:返回值的引用是获取自参数,这就意味着参数和返回值的生命周期是一样的。道理很简单,我们能看出来,编译器自然也能看出来,因此,就算我们不标注生命周期,也不会产生歧义。

只不过,消除规则不是万能的,若编译器不能确定某件事是正确时,会直接判为不正确,那么你还是需要手动标注生命周期
函数或者方法中,参数的生命周期被称为 输入生命周期,返回值的生命周期被称为 输出生命周期

三条消除原则

1.每一个引用参数都会获得独自的生命周期

例如一个引用参数的函数就有一个生命周期标注: fn foo<'a>(x: &'a i32),两个引用参数的有两个生命周期标注:fn foo<'a, 'b>(x: &'a i32, y: &'b i32), 依此类推。

2.若只有一个输入生命周期(函数参数中只有一个引用类型),那么该生命周期会被赋给所有的输出生命周期,也就是所有返回值的生命周期都等于该输入生命周期

例如函数 fn foo(x: &i32) -> &i32,x 参数的生命周期会被自动赋给返回值 &i32,因此该函数等同于 fn foo<'a>(x: &'a i32) -> &'a i32

3.若存在多个输入生命周期,且其中一个是 &self 或 &mut self,则 &self 的生命周期被赋给所有的输出生命周期。

拥有 &self 形式的参数,说明该函数是一个 方法,该规则让方法的使用便利度大幅提升。

让我们假装自己是编译器,然后看下以下的函数该如何应用这些规则:

例子1

fn first_word(s: &str) -> &str // 实际项目中的手写代码

首先,我们手写的代码如上所示时,编译器会先应用第一条规则,为每个参数标注一个生命周期:

fn first_word<'a>(s: &'a str) -> &str  // 编译器自动为参数添加生命周期

此时,第二条规则就可以进行应用,因为函数只有一个输入生命周期,因此该生命周期会被赋予所有的输出生命周期:

fn first_word<'a>(s: &'a str) -> &'a str  // 编译器自动为返回值添加生命周期

此时,编译器为函数签名中的所有引用都自动添加了具体的生命周期,因此编译通过,且用户无需手动去标注生命周期,只要按照 fn first_word(s: &str) -> &str的形式写代码即可。

例子2

fn longest(x: &str, y: &str) -> &str // 实际项目中的手写代码

首先,编译器会应用第一条规则,为每个参数都标注生命周期:

fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str 

但是此时,第二条规则却无法被使用,因为输入生命周期有两个,第三条规则也不符合,因为它是函数,不是方法,因此没有 &self 参数。在套用所有规则后,编译器依然无法为返回值标注合适的生命周期,因此,编译器就会报错,提示我们需要手动标注生命周期。

例子3

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

首先,编译器应用第一规则,给予每个输入参数一个生命周期。

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part<'b>(&'a self, announcement: &'b str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

需要注意的是,编译器不知道 announcement 的生命周期到底多长,因此它无法简单的给予它生命周期 'a,而是重新声明了一个全新的生命周期 'b。接着,编译器应用第三规则,将 &self 的生命周期赋给返回值 &str

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part<'b>(&'a self, announcement: &'b str) -> &'a str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

尽管我们没有给方法标注生命周期,但是在第一和第三规则的配合下,编译器依然完美的为我们亮起了绿灯。

生命周期约束

我们来看下面这个例子。将返回值的生命周期声明为’b,但是实际返回的是生命周期为’a的self.part。

impl<'a: 'b, 'b> ImportantExcerpt<'a> {
    fn announce_and_return_part(&'a self, announcement: &'b str) -> &'b str {
        println!("Attention please: {}", announcement);
        self.part
    }
}
  • 'a: 'b,是生命周期约束语法,跟泛型约束非常相似,用于说明 'a 必须比 'b 活得久
  • 可以把 'a 和 'b 都在同一个地方声明(如上),或者分开声明但通过 where 'a: 'b 约束生命周期关系,如下:
impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part<'b>(&'a self, announcement: &'b str) -> &'b str
    where
        'a: 'b,
    {
        println!("Attention please: {}", announcement);
        self.part
    }
}

加上这个约束,告诉编译器’a活的比’b更久,引用’a不会产生悬垂指针(无效引用)。

静态生命周期

rust中有一个非常特殊的生命周期,那就是’static,拥有该生命周期的引用可以活的和整个程序一样久。实际上字符串字面值就拥有’static生命周期,它被硬编码进rust的二进制文件中。'static生命周期非常强大,随意使用它相当于放弃了生命周期检查。遇到因为生命周期导致的编译不通过问题,首先想的应该是:是否是我们试图创建一个悬垂引用,或者是试图匹配不一致的生命周期,而不是简单粗暴的用 'static 来解决问题。除非实在遇到解决不了的生命周期标注问题,可以尝试’static生命周期。例如:

fn t() -> &'static str{
    "qwert"
}

fn t() -> &'static str{
    "qwert"
}

注意,使用’static生命周期的时候,不需要提前声明。

一个复杂例子: 泛型,特征约束以及生命周期

use std::fmt::Display;

fn longest_with_an_announcement<'a, T>(
    x: &'a str,
    y: &'a str,
    ann: T,
) -> &'a str
where
    T: Display,
{
    println!("Announcement! {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

例子中,包含了生命周期’a,泛型T以及对T的约束Display(因为我们需要打印ann)。

到此这篇关于rust生命周期的文章就介绍到这了,更多相关rust生命周期内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: rust生命周期详解

本文链接: https://www.lsjlt.com/news/200315.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • rust生命周期详解
    目录rust生命周期借用检查函数中的生命周期手动声明生命周期结构体中的生命周期生命周期消除三条消除原则生命周期约束静态生命周期rust生命周期 生命周期是rust中用来规定引用的有效...
    99+
    2023-03-19
    rust生命周期
  • Rust指南之生命周期机制详解
    目录前言1、所有权中的垂悬引用解析2、结构体中使用String 而不用&str 的原因3、生命周期注释4、结构体中使用字符串切片引用5、静态生命周期6、泛型、特性与生命周期综...
    99+
    2022-11-13
    Rust生命周期 Rust生命周期机制
  • 深入了解Rust的生命周期
    Rust生命周期简介 Rust 中的每一个引用都有其 生命周期(lifetime),也就是引用保持有效的作用域。 生命周期的主要目标是避免悬垂引用,它会导致程序引用了非预期引用的数据...
    99+
    2022-12-14
    Rust 生命周期机制 Rust 生命周期
  • 解析Rust struct 中的生命周期
    最近在用rust 写一个redis的数据校验工具。redis-rs中具备 redis::ConnectionLike trait,借助它可以较好的来抽象校验过程。在开发中,不免要定义...
    99+
    2022-11-13
  • rust生命周期源码分析
    本文小编为大家详细介绍“rust生命周期源码分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“rust生命周期源码分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。rust生命周期生命周期是rust中用来规定引...
    99+
    2023-07-05
  • Vue生命周期详解
    这篇博客将会从下面四个常见的应用诠释组件的生命周期,以及各个生命周期应该干什么事 单组件的生命周期父子组件的生命周期兄弟组件的生命周期宏mixin的生命周期 生命周期:Vue 实例从...
    99+
    2022-11-13
  • 详解vue生命周期
    目录为什么要理解生命周期什么是生命周期生命周期钩子函数created和mounted钩子的一些实战用法1.异步函数 2.Vue.nextTick对异步函数的结果进行操作 总结为什么要...
    99+
    2022-11-12
  • Rust的生命周期是怎样的
    这篇文章主要介绍“Rust的生命周期是怎样的”,在日常操作中,相信很多人在Rust的生命周期是怎样的问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Rust的生命周期是怎样的”的疑惑有所帮助!接下来,请跟着小编...
    99+
    2023-06-27
  • React的生命周期详解
    一、React生命周期 React 生命周期分为三种状态 1. 初始化2.更新3.销毁 初始化 1、getDefaultProps() 设置默认的props,也可以用dufault...
    99+
    2022-11-13
  • 《Android》Activity生命周期详解
    1、什么是Activity的生命周期? 用户在使用一个应用程序时,随着应用的启动、页面的跳转、进入后台等一些操作的发生,Activity会回调一...
    99+
    2022-06-06
    activity生命周期 activity Android
  • Android Activity生命周期详解
    Activity 的生命周期。 一、理解Activity Activity是Android程序的4大组件之一。 Activity是Android程序的表示层。程序的每一个显...
    99+
    2022-06-06
    activity生命周期 activity Android
  • Android Service生命周期详解
    引言 应用程序组件有一个生命周期——一开始Android实例化他们响应意图,直到结束实例被销毁。在这期间,他们有时候处于激活状态,有时候处于非激活状 态;对于活动,对用户有时...
    99+
    2022-06-06
    android service service service生命周期 Android
  • 详解uniapp的生命周期
    Uniapp作为一款跨平台应用开发框架,具有丰富的生命周期,以下是Uniapp的生命周期: 1.应用生命周期 应用生命周期是指应用程序从启动到关闭的整个过程,包括应用程序的启动、前后...
    99+
    2023-05-17
    vue uniapp uniapp生命周期
  • 微信APP生命周期及页面生命周期示例详解
    目录官方文档小程序的启动流程app生命周期页面的生命周期页面的生命周期(图)官方文档 ogram&jumpbackUrl=%2Fdoc%2F" rel="external n...
    99+
    2022-11-13
  • Rust语言生命周期的示例分析
    本篇文章为大家展示了Rust语言生命周期的示例分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Rust 官方博客发布了 2020 年度的 Rust 调查报告。此次...
    99+
    2022-10-19
  • Android Fragment的生命周期详解
    Fragments的生命周期        每一个fragments 都有自己的一套生命周期回调方法和处理自己的用...
    99+
    2022-06-06
    android fragment fragment Android
  • Vue生命周期区别详解
    生命周期分类 vue每个组件都是独立的,每个组件都有一个属于它的生命周期, 从一个组件创建、数据初始化、挂载、更新、销毁,这就是一个组件所谓的生命周期。 在组件中具体的方法有: ...
    99+
    2022-11-12
  • React中的生命周期详解
    目录react生命周期常用的生命周期不常用的生命周完整的生命周期图react生命周期 函数组件无生命周期,生命周期只有类组件才拥有 生命周期函数指在某一时刻组件会自动调用并执行的函数...
    99+
    2022-11-13
  • vue的生命周期钩子与父子组件的生命周期详解
    目录vue的生命周期钩子的介绍父子组件的生命周期加载渲染过程父组件更新过程子组件更新过程父子组件更新过程销毁过程代码示例created和mounted的区别vue的生命周期钩子的介绍...
    99+
    2022-11-13
    vue 生命周期 vue 父子组件生命周期
  • Vue.js的生命周期详细讲解
    本篇内容主要讲解“Vue.js的生命周期详细讲解”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Vue.js的生命周期详细讲解”吧!用Vue框架,熟悉它的生命周期...
    99+
    2022-10-19
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作