Rust学习-生命周期

这篇具有很好参考价值的文章主要介绍了Rust学习-生命周期。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Rust 最与众不同的功能

之前学习中,有多种可能类型时必须注明类型;同理,引用的生命周期以一些不同方式相关联时,需要使用泛型生命周期参数来注明关系,这样就能确保运行时实际使用的引用有效

避免悬垂引用

{
    // 声明了没有初始值的变量,这些变量存在于外部作用域
    // 尝试在给它一个值之前使用这个变量,会出现一个编译错误
    //  Rust 不允许空值
    let r;

    {
        let x = 5;
        r = &x;
         // r 引用的值在尝试使用之前就离开了作用域
    }

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

Rust 编译器有一个 借用检查器(borrow checker)
它比较作用域来确保所有的借用都是有效

泛型生命周期

下文编译不通过,返回值需要一个泛型生命周期参数
生命周期标注并不改变任何引用的生命周期的长短
生命周期标注描述多个引用生命周期相互的关系,不影响其生命周期

示例

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

	// 获取作为引用的字符串 slice,因为不希望 longest 函数获取参数的所有
	// 期望该函数接受 String 的 slice(参数 string1 的类型)和字符串字面量
    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

// error[E0106]: missing lifetime specifier
// fn longest(x: &str, y: &str) -> &str {
//    |       ----     ----     ^ expected named lifetime parameter
//  help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
// help: consider introducing a named lifetime parameter
// 返回值需要一个泛型生命周期参数,Rust 并不知道要返回的引用指向 x/y
// 为了修复这个错误,将增加泛型生命周期参数来定义引用间的关系以便借用检查器可以进行分析

生命周期的添加

// 位于引用的 & 之后,并有一个空格来将引用类型与生命周期标注分隔开
&i32        // 引用
&'a i32     // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用

单个生命周期标注本身没有多少意义,生命周期标注告诉 Rust 多个引用的泛型生命周期参数如何相互联系
如果函数有一个生命周期 'a 的 i32 的引用的参数 first,有一个生命周期 'a 的 i32 的引用的参数 second,这两生命周期标注意味着引用 first 和 second 必须与这泛型生命周期存在一样久

函数签名中的生命周期

当函数签名中指定了泛型类型参数后就可以接受任何类型
当指定了泛型生命周期后函数能接受任何生命周期的引用

【手动标记生命周期的原因】
当函数引用或被函数之外的代码引用时,让 Rust 自身分析出参数或返回值的生命周期几乎是不可能的。这些生命周期在每次函数被调用时都可能不同。这也就是为什么需要手动标记生命周期

// 像泛型类型参数,泛型生命周期参数需要声明在函数名和参数列表间的尖括号中
// 它告诉 Rust,关于参数中引用和返回值之间的限制是他们都必须拥有相同的生命周期
// longest 函数返回的引用的生命周期与传入该函数的引用的生命周期的较小者一致
// 即当具体引用被传递给 longest 时,被 'a 所替代的具体生命周期是 x 的作用域与 y 的作用域相重叠的那一部分
// 泛型生命周期 'a 的具体生命周期等同于 x 和 y 的生命周期中较小的那一个
// 因为用相同的生命周期参数 'a 标注了返回的引用值,所以返回的引用值就能保证在 x 和 y 中较短的那个生命周期结束之前保持有效
// 
// 通过在函数签名中指定生命周期参数时,并没有改变任何传入值或返回值的生命周期
// 指出任何不满足这个约束条件的值都将被借用检查器拒绝
// 在函数中使用生命周期标注时,标注出现在函数签名中,不存在于函数体中任何代码中
// 参数和返回值都是字符串slice

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    // string1 的作用域较长
    let string1 = String::from("long string is long");

    {
        // string2的 作用域仅限于此
        let string2 = String::from("xyz");
        // result的作用域同string2
        let result = longest(string1.as_str(), string2.as_str());
        // 正常打印
        println!("The longest string is {}", result);
    }
    
    // 如下将编译失败
    // let result;
    // {
    //     let string2 = String::from("xyz");
    //     result = longest(string1.as_str(), string2.as_str());
    // }
    // println!("The longest string is {}", result);
    // error[E0597]: `string2` does not live long enough
}

// 打印结果如下:
// The longest string is long string is long

生命周期绑定到函数部分参数

// 如果将 longest 函数的实现修改为总是返回第一个参数而不是最长的字符串 slice
// 就不需要为参数 y 指定一个生命周期
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
    x
}

返回的引用没有指向任何输入参数

当从函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配
如果返回的引用没有指向任何参数,唯一的可能就是它指向一个函数内部创建的值,它将会是一个悬垂引用,会在函数结束时离开作用域

fn longest<'a>(x: &str, y: &str) -> &'a str {
    // 即便为返回值指定了生命周期参数 'a,编译失败
    // 因为返回值的生命周期与参数完全没有关联
    // error[E0597]: `result` does not live long enough
    let result = String::from("really long string");
    // result 在 longest 函数的结尾将离开作用域并被清理
    // 而尝试从函数返回一个 result 的引用,无法指定生命周期参数来改变悬垂引用
    result.as_str()
}

解决方案:返回一个有所有权的数据类型而不是一个引用,由函数调用者清理它

总结

生命周期语法用于将函数的多个参数与其返回值的生命周期进行关联。
一旦他们形成了某种关联,Rust 就有了足够的信息来允许内存安全的操作并阻止会产生悬垂指针亦或是违反内存安全的行为

结构体定义中的生命周期标注

// 定义包含引用的结构体,不过需要为结构体定义中的每一个引用添加生命周期标注
// 必须在结构体名称后面的尖括号中声明泛型生命周期参数,在结构体定义中使用生命周期参数
struct ImportantExcerpt<'a> {
    // 存放一个字符串 slice
    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 '.'");
    // ImportantExcerpt 的实例i不能比其 part 字段中的引用存在的更久
    let i = ImportantExcerpt { part: first_sentence };
}

生命周期的省略

每一个引用都有一个生命周期
需要为那些使用了引用的函数或结构体指定生命周期
但下文没有指定,但是编译正常,这就是生命周期的省略

【历史原因】
早期版本(pre-1.0)的 Rust ,不能编译
Rust 团队发现特定情况下开发者总是重复地编写一模一样的生命周期标注
这些场景可预测且遵循几个明确的模式。
Rust 团队就把这些模式编码进了 Rust 编译器中,如此借用检查器在这些情况下就能推断出生命周期而不再强制开发者显式的增加标注。
未来只会需要更少的生命周期标注。
如果 Rust 在明确遵守这些规则的前提下变量的生命周期仍然是模棱两可的话,它不会猜测剩余引用的生命周期应该是什么,直接编译报错

【概念】
输入生命周期(input lifetimes) :函数或方法的参数的生命周期
输出生命周期(output lifetimes):返回值的生命周期
生命周期省略规则(lifetime elision rules):被编码进 Rust 引用分析的模式。这并不是需要开发者遵守的规则;这些规则是一系列特定的场景,此时编译器会考虑,如果代码符合这些场景,就无需明确指定生命周期。

【生命周期规则】
(1)输入:每一个是引用的参数都有它自己的生命周期参数
(2)输出:只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数
(3)输出:如果方法有多个输入生命周期参数并且其中一个参数是 &self 或 &mut self,说明是个对象的方法(method), 那么所有输出生命周期参数被赋予 self 的生命周期

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[..]
}

它的隐式解析如下:

// 开始时签名中的引用并没有关联任何生命周期
fn first_word(s: &str) -> &str {

// 编译器应用第一条规则:每个引用参数都有其自己的生命周期,假设规则为a:
fn first_word<'a>(s: &'a str) -> &str {

// 编译器应用第二条规则:输入参数的生命周期将被赋予输出生命周期参数:
fn first_word<'a>(s: &'a str) -> &'a str {

编译器可以继续它的分析而无须开发者标记这个函数签名中的生命周期

另一个例子

fn longest(x: &str, y: &str) -> &str {
// 应用规则1后就变成如下,但是规则2不适用,报错
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {

方法定义中的生命周期标注

为结构体实现方法时,结构体字段的生命周期必须总是在 impl 关键字之后声明,在结构体名称之后被使用

struct ImportantExcerpt<'a> {
    // 存放一个字符串 slice
    part: &'a str,
}

impl<'a> ImportantExcerpt<'a> {
    // 应用第一条生命周期规则,并不必须标注 self 引用的生命周期
    fn level(&self) -> i32 {
        3
    }
}

规则1、3

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}
// 应用第一条生命周期省略规则并给予 &self 和 announcement 各自的生命周期
// 其中一个参数是 &self,返回值类型被赋予了 &self 的生命周期

静态生命周期

'static,其生命周期能够存活于整个程序期间
所有的字符串字面量都拥有 'static 生命周期

// 这个字符串的文本被直接储存在程序的二进制文件中而这个文件总是可用的
// 所有的字符串字面量都是 'static
let s: &'static str = "I have a static lifetime.";

将引用指定为 'static 之前,思考一下这个引用是否真的在整个程序的生命周期里都有效
代码中的问题是尝试创建一个悬垂引用或者可用的生命周期不匹配,解决这些问题而不是指定一个 'static 的生命周期文章来源地址https://www.toymoban.com/news/detail-568858.html

结合泛型类型参数、trait bounds 和生命周期

use std::fmt::Display;

// ann 的类型是泛型 T,任何实现了Display trait的类型实例都可以放进去
// 生命周期参数 'a 和泛型类型参数 T 都位于函数名后的同一尖括号列表中
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
    }
}

到了这里,关于Rust学习-生命周期的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Rust之泛型、trait与生命周期

    泛型是具体类型或其他属性的抽象替代。在编写代码时,可以直接描述泛型的行为,或者它与其他泛型产生的联系,而无须知晓它在编译和运行代码时采用的具体类型。 们可以在声明函数签名或结构体等元素时使用泛型,并在随后搭配不同的具体类型来使用这些元素。 当使

    2024年02月13日
    浏览(39)
  • rust踩雷笔记3——生命周期的理解

    生命周期是rust中最难的概念——鲁迅 这一块内容即便是看rust圣经,第一遍也有点懵。今天早上二刷突然有了更直观的认识,记录一下。 概念和基本使用 生命周期就是应用的有效作用域,它的主要作用是避免悬垂引用。 悬垂引用的典型例子: 简而言之就是y引用的变量在y被

    2024年02月12日
    浏览(30)
  • Rust in Action笔记 第四章生命周期、所有权、借用

    第四章用了一个行星通信的例子来阐述整个主题,主要角色有地面站(Ground station)、人造卫星(CubeSat),两者有不同的状态并且能互相发消息通信; Rust有类型安全(type safety)机制来检查函数的类型和返回值,如果一个自定义类型(用struct声明的)包含了非内置类型(如

    2024年02月09日
    浏览(42)
  • 文盘Rust -- 生命周期问题引发的 static hashmap 锁 | 京东云技术团队

    2021年上半年,撸了个rust cli开发的框架,基本上把交互模式,子命令提示这些cli该有的常用功能做进去了。项目地址:https://github.com/jiashiwen/interactcli-rs。 春节以前看到axum已经0.4.x了,于是想看看能不能用rust做个服务端的框架。 春节后开始动手,在做的过程中会碰到各种有趣

    2024年02月09日
    浏览(38)
  • Rust之泛型、特性和生命期(四):验证有生存期的引用

      Windows 10 Rust 1.71.0   VS Code 1.80.1 这里继续沿用上次工程rust-demo 生存期是我们已经在使用的另一种泛型。生存期不是确保一个类型具有我们想要的行为,而是确保引用在我们需要时有效。 我们在第4章“引用和借用”一节中没有讨论的一个细节是,Rust中的每个引用都有一个生

    2024年02月16日
    浏览(46)
  • Rust之泛型、特性和生命期(三):Traits:定义共同的行为

    Windows 10 Rust 1.70.0    VS Code 1.79.2 这里继续沿用上次工程rust-demo Trait定义了一个特定类型所具有的功能,并且可以与其他类型共享。我们可以使用特质以抽象的方式来定义共享行为。我们可以使用特质的界限来指定一个通用类型可以是任何具有某些行为的类型。  注意:trait

    2024年02月09日
    浏览(51)
  • 【Rust学习】安装Rust环境

    本笔记为了记录学习Rust过程,内容如有错误请大佬指教 使用IDE:vs code 参考教程:菜鸟教程链接: 菜鸟教程链接: 因为我已经安装过VSCode了,所以VSCode的安装方法在此处就不多介绍了,接下来就是安装Rust的编译工具。 Rust 编译工具 可以点击跳转下载Rust 编译工具 新建文件夹,

    2024年01月17日
    浏览(63)
  • 【Unity学习笔记】生命周期

    官方文档:事件函数的执行顺序 如图: 脚本的生命周期主要经历以下几个阶段: 初始化阶段,(包括初始化Awake,OnEnable,然后Editor的Reset被穿插在着初始化过程之间,因此我们可以在脚本里重写Reset方法,这将在编辑器中的game的start之前执行。最后Start),当我们开始游戏的

    2024年02月14日
    浏览(40)
  • 学习Vue:组件生命周期

    在Vue.js中,组件的生命周期是指组件从创建到销毁的整个过程,而生命周期钩子函数则是在不同阶段执行的函数,允许您在特定时间点添加自定义逻辑。本文将详细介绍组件的生命周期以及常用的生命周期钩子函数。 组件的生命周期可以分为以下几个阶段: 创建阶段: 在这

    2024年02月12日
    浏览(41)
  • 【Rust】Rust学习 第十七章Rust 的面向对象特性

    面向对象编程(Object-Oriented Programming,OOP)是一种模式化编程方式。对象(Object)来源于 20 世纪 60 年代的 Simula 编程语言。这些对象影响了 Alan Kay 的编程架构中对象之间的消息传递。他在 1967 年创造了  面向对象编程  这个术语来描述这种架构。关于 OOP 是什么有很多相互矛

    2024年02月11日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包