【Rust自学】10.7. 生命周期 Pt.3:输入输出生命周期与3规则

news2025/1/11 2:44:53

喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

10.7.1. 深入理解生命周期

1.指定生命周期参数的方式依赖于函数所做的事情

以上一篇文章的代码为例子:

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

这里的函数签名之所以这么写是因为不确定返回值到底是x还是y。如果我修改代码,比如把返回值固定为x那么就没必要给y写一个显式生命周期了:

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

所以这个代码的函数签名就没有给y限制生命周期。

2.当函数返回引用时,返回类型的生命周期参数需要与其中一个生命周期匹配

如果返回的引用没有指向任何参数,返回的内容就会变成悬空引用,因为在函数内创建的值在函数结束的时候就离开了作用域,返回的引用指向的就是被释放的内存。

看个例子:

fn longest<'a>(x: &'a str, y: &str) -> &'a str {  
    let result = String::from("Something");
    result.as_str()
}

在这个函数里创建了一个String类型的result,然后调用result上的as_str方法返回字符串切片(&str),其实就是一个引用,然后就报错了:

error[E0515]: cannot return value referencing local variable `result`
  --> src/main.rs:13:5
   |
13 |     result.as_str()
   |     ------^^^^^^^^^
   |     |
   |     returns a value referencing data owned by the current function
   |     `result` is borrowed here

报错信息是无法返回引用本地变量result的值,因为这块返回的值是函数内部持有的数据,其实就是刚才说的原因,当内部数据离开作用域后就会被清除。

那如果我就想要把函数内部创建值作为返回值改怎么写呢?那就不返回引用,直接返回这个值:

fn longest(x: &str, y: &str) -> String {  
    let result = String::from("Something");
    result
}

这样就相当于把函数的所有权移交给调用者了,要清理这块内存就由调用者来清理。这样写也不需要显式声明声明周期了,因为返回值与参数根本没关系,而且只有引用才有生命周期问题。

通过这个例子可以看到,生命周期的语法在根本上就是用来关联函数的不同参数以及返回值之间的生命周期的。 一旦它们取得了某种联系,Rust就获得了足够的信息来支持保证内存安全的操作并且组织可能会导致悬垂指针或是其他破坏内存安全的操作。

10.7.2. 结构体中的生命周期标注

在前面的文章里,我们在结构体中只定义过自持有的类型,比如i32String。而实际上结构体的字段也可以是引用类型,如果是引用的话就需要在每个引用上添加生命周期标注。

看个例子:

struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}

ImportantExcerpt下只有一个字段part,其类型是字符串切片,也就是一个引用类型。因为它是引用类型,所以就需要标注生命周期。

生命周期标注的方法和泛型一样,就在结构体后面加<>,在里面写生命周期泛型类型参数即可,这里写的是'apart这个引用必须要比这个结构体实例的存活时间要长。因为只要实例存在,就会一直有part这个引用,如果part先没有,那么实例肯定会出错。

main函数,里面先创建了一个String类型的novel然后通过splitnext方法来提取出这个字符串里的第一个句子(unwrap是用来解包Option类型的,在 9.2. Result枚举与可恢复的错误 Pt.1 中有过介绍)。这个句子的类型是&str,也就是一个引用。然后创建了ImportantExcerpt这一结构体的实例i,把这个引用作为part字段的值。

这样写是没有错误的,因为first_sentence这个引用的作用域是从第7行到第11行,而i的作用域是从第8行到第11行,所以说part这个字段的存活时间比实例长并且能完全覆盖i的生命周期。

10.7.3. 生命周期的省略

每个引用都有生命周期,并且需要为使用生命周期的函数或结构体指定生命周期参数。

那为什么这段代码(来自 4.5. 切片(Slice))没有生命周期也能通过编译呢:

fn main() {
	let s = String::from("Hello world");
	let word = first_word(&s);
	println!("{}", word);
}
fn first_word(s:&str) -> &str {
	let bytes = s.as_bytes();
	for (i, &item) in bytes.iter().enumerate() {
		if item == b' ' {
			return &s[..i];
		} 
	}
	&s[..]
}

这个函数在没有生命周期注释的情况下编译的原因是有历史的:在 Rust 的早期版本(1.0 之前)中,这个代码不会通过编译,因为当时要求每个引用都需要一个显式的生命周期。函数签名就得这样写:

fn first_word<'a>(s: &'a str) -> &'a str {

后来Rust团队发现在某些特定情况下Rust程序员总会一遍又一遍地写同样的生命周期标注,而且这些场景是可预测的,这些场景有一些明确的模式,于是Rust团队就将这些模式直接写入了编译器代码,使得借用检查器在这些情况下可以自动地推导生命周期,而无需程序员显式标注。

了解这段历史的意义在于未来可能会有更多确定性模式可能会出现并被添加到编译器中。将来,可能需要更少的生命周期注释(谢天谢地)。

刚才说的这些在Rust引用分析中所编入的模式称为生命周期省略规则。这些规则无需程序员来遵守,它们是一些特殊情况,由编译器来考虑。如果你的代码符合这些情况,那就无需显式标注生命周期。

但是生命周期省略规则不会提供完整的推断,如果在应用了这个规则以后,引用的生命周期仍然模糊不清,那么仍然会引发编译错误。解决办法就是手动添加生命周期,表明引用间的相互关系。

10.7.4. 输入、输出生命周期

如果生命周期出现在函数/方法的参数中,那么这类生命周期就叫做输入生命周期

如果它出现在函数/方法的返回值中,那么就叫做输出生命周期

10.7.5. 生命周期省略的三个规则

编译器使用3个规则在没有显式标注生命周期的情况下来确定引用的生命周期

  • 规则1用于输入生命周期
  • 规则2、3用于输出生周期
  • 如果编译器在应用完3个规则后仍然有无法确定生命周期的引用,就会报错
  • 这3个规则不但适用于函数或是方法的定义,也适用于impl

规则1: 每个引用类型的参数都有自己的生命周期。 单参数的函数就有1个生命周期,双参数的函数就有两个,以此类推。

规则2: 如果只有1个输入生命周期参数,那么该生命周期被赋给所有的输出生命周期参数。 就是单参数的生命周期只有1个,这个生命周期就是这个函数所有可能返回值的生命周期。

规则3: 如果有多个输入生命周期参数,但其中一个是&self&mut self(也就是说是这个函数是方法),那么self的生命周期会被赋给所有输出的生命周期参数。

1. 成功例

规则讲完,看看例子:

fn first_word(s:&str) -> &str {
	//...
}

把自己带入一下编译器,想想对于这个函数签名如何根据3条规则来找到省略的生命周期。

首先应用第一条规则——每个引用类型的参数都有自己的生命周期。这里只有一个参数,所以就只有一个生命周期。所以到这一步编译器推断出了:

fn first_word<'a>(s:&'a str) -> &str {
	//...
}

由于只有一个输入生命周期,所以第2条规则在这里也适用——如果只有1个输入生命周期参数,那么该生命周期被赋给所有的输出生命周期参数。 所以输入生命周期就被赋予给了输出生命周期。到这一步编译器推断出了:

fn first_word<'a>(s:&'a str) -> &'a str {
	//...
}

由于只有一个输入生命周期,且这个函数不是方法,所以第3条不适用

而现在函数中所有的引用都有了生命周期,因此编译器就可以继续分析代码,而无需程序员手动标注这个函数签名里的生命周期。

2. 失败例

来看第二个例子:

fn longest(x:&str, y:&str) -> &str {
	//...
}

这个函数签名有两个引用输入,返回类型也是引用。尝试用这3条规则:

首先应用第一条规则——每个引用类型的参数都有自己的生命周期。这里有两个参数,就有两个生命周期:

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

由于有两个引用参数,所以规则2不适用。

由于这个函数不是方法,所以规则3不适用。

应用完这3条规则后发现返回值的生命周期仍然无法确定,所以编译器就会报错。也就是说你必须显式声明生命周期。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2274661.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

创建并配置华为云虚拟私有云

目录 私有云 创建虚拟私有云 私有云 私有云是一种云计算模式&#xff0c;它将云服务部署在企业或组织内部的私有基础设施上&#xff0c;仅供该企业或组织内部使用&#xff0c;不对外提供服务.私有云的主要特点包括&#xff1a; 私密性&#xff1a;私有云的资源&#xff08;如…

子父组件传值

Angular 2 及以上版本中的父子组件通信方式 在 Angular 2 及以上版本中&#xff0c;父子组件通信主要通过以下几种方式实现&#xff1a; 一、使用Input()进行父向子通信 父组件通过属性绑定的方式将数据传递给子组件&#xff0c;子组件使用Input()装饰器来接收这些数据。 二…

wireshark排除私接小路由

1.wireshark打开&#xff0c;发现了可疑地址&#xff0c;合法的地址段DHCP是192.168.100.0段的&#xff0c;打开后查看发现可疑地址段&#xff0c;分别是&#xff0c;192.168.0.1 192.168.1.174 192.168.1.1。查找到它对应的MAC地址。 ip.src192.168.1.1 2.通过show fdb p…

机器学习实战——决策树:从原理到应用的深度解析

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​​ ​​​ ​​ 决策树&#xff08;Decision Tree&#xff09;是一种简单而直观的分类与回归模型&#xff0c;在机器学习中广泛应用。它的…

2025年01月09日Github流行趋势

1. 项目名称&#xff1a;khoj 项目地址url&#xff1a;https://github.com/khoj-ai/khoj项目语言&#xff1a;Python历史star数&#xff1a;22750今日star数&#xff1a;1272项目维护者&#xff1a;debanjum, sabaimran, MythicalCow, aam-at, eltociear项目简介&#xff1a;你…

用python实现烟花代码,完整代码拿走不谢

有时候用python实现一些有趣的代码&#xff0c;既有趣&#xff0c;又能提升知识 使用Python实现动态烟花代码 效果如下&#xff1a; 不废话&#xff0c;直接上代码&#xff1a; import pygame from random import randint, uniform, choice import mathvector pygame.math…

Python机器学习笔记(十八、交互特征与多项式特征)

添加原始数据的交互特征&#xff08;interaction feature&#xff09;和多项式特征&#xff08;polynomial feature&#xff09;可以丰富特征表示&#xff0c;特别是对于线性模型。这种特征工程可以用统计建模和许多实际的机器学习应用中。 上一次学习&#xff1a;线性模型对w…

数据结构——栈的实现

今天&#xff0c;我们来写一下关于栈的博文。 1.首先我们先了解一下什么是栈&#xff1f; 一&#xff1a;概念&#xff1a; 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端称为栈顶&#xff0c;另…

电脑提示directx错误导致玩不了游戏怎么办?dx出错的解决方法

想必大家都有过这样的崩溃瞬间&#xff1a;满心欢喜打开心仪的游戏&#xff0c;准备在虚拟世界里大杀四方或者畅游冒险&#xff0c;结果屏幕上突然弹出个 DirectX 错误的提示框&#xff0c;紧接着游戏闪退&#xff0c;一切美好戛然而止。DirectX 作为 Windows 系统下游戏运行的…

python学opencv|读取图像(二十九)使用cv2.getRotationMatrix2D()函数旋转缩放图像

【1】引言 前序已经学习了如何平移图像&#xff0c;相关文章链接为&#xff1a; python学opencv|读取图像&#xff08;二十七&#xff09;使用cv2.warpAffine&#xff08;&#xff09;函数平移图像-CSDN博客 在此基础上&#xff0c;我们尝试旋转图像的同时缩放图像。 【2】…

MySQL表的增删查改(下)——Update(更新),Delete(删除)

文章目录 Update将孙悟空同学的数学成绩修改为80分将曹孟德同学的数学成绩变更为 60 分&#xff0c;语文成绩变更为 70 分将总成绩倒数前三的 3 位同学的数学成绩加上 30 分将所有同学的语文成绩更新为原来的 2 倍 Delete删除数据删除孙悟空同学的考试成绩删除整张表数据 截断表…

Virgo:增强慢思考推理能力的多模态大语言模型

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

聚类系列 (二)——HDBSCAN算法详解

在进行组会汇报的时候&#xff0c;为了引出本研究动机&#xff08;论文尚未发表&#xff0c;暂不介绍&#xff09;&#xff0c;需要对DBSCAN、OPTICS、和HDBSCAN算法等进行详细介绍。在查询相关资料的时候&#xff0c;发现网络上对于DBSCAN算法的介绍非常多与细致&#xff0c;但…

零基础 监控数据可视化 Spring Boot 2.x(Actuator + Prometheus + Grafana手把手) (上)

一、安装Prometheus Releases prometheus/prometheus GitHubhttps://github.com/prometheus/prometheus/releases 或 https://prometheus.io/download/https://prometheus.io/download/ 1. 下载适用于 Windows 的二进制文件&#xff1a; 找到最新版本的发布页面&#xf…

解决Qt打印中文字符出现乱码

在 Windows 平台上&#xff0c;默认的控制台编码可能不是 UTF-8&#xff0c;这可能会导致中文字符的显示问题。 下面是在 Qt 应用程序中设置中文字体&#xff0c;并确保控制台输出为 UTF-8 编码&#xff1a; 1. Qt 应用程序代码 在 Qt 中&#xff0c;我们可以使用 QApplic…

PDFelement 特别版

Wondershare PDFelement Pro 是一款非常强大的PDF编辑软件&#xff0c;它允许用户轻松地编辑、转换、创建和管理PDF文件。这个中文特别版的软件具有许多令人印象深刻的功能&#xff0c;PDFelement Pro 提供了丰富的编辑功能&#xff0c;可以帮助用户直接在PDF文件中添加、删除、…

SpringBoot-Web入门-入门程序

1.如何创建一个springBoot-Web工程&#xff1f; 实战演示&#xff1a; 新建一个模块&#xff0c;找到Spring Boot选项 点击下一步之后&#xff0c;选择勾选对应的依赖。我这里勾选的是web下的Spring Web 创建完毕之后&#xff0c;在src的main下的java对应的包下创建一个Contro…

从光子到图像——相机如何捕获世界?

引言 你是否想过为何我们按一下相机快门就可以将眼前广袤多彩的世界显示于一个小小的相机屏幕上&#xff1f;本期推文中将带着大家重现从光子转换为电子、电子转换为图像中数字驱动值的整个流程。 ▲人们通过相机捕获眼前的场景 从光子到电子的转换 光线首先通过光学镜头进入相…

《机器学习》——贝叶斯算法

贝叶斯简介 贝叶斯公式&#xff0c;又称贝叶斯定理、贝叶斯法则&#xff0c;最初是用来描述两个事件的条件概率间的关系的公式&#xff0c;后来被人们发现具有很深刻的实际意义和应用价值。该公式的实际内涵是&#xff0c;支持某项属性的事件发生得愈多&#xff0c;则该属性成…

【非常详细】TCP/IP协议详解

一、TCP/IP简介 TCP/IP&#xff08;传输控制协议/互联网协议&#xff09;是一种用于连接网络设备的协议族&#xff0c;广泛应用于互联网和局域网中。它提供了在不同类型的网络上进行通信的标准和方法。 二、TCP/IP模型 TCP/IP在数据包设计上采用封装和分用的策略&#xff0c;…