当前位置: 首页 > news >正文

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

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

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条规则后发现返回值的生命周期仍然无法确定,所以编译器就会报错。也就是说你必须显式声明生命周期。

相关文章:

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

喜欢的话别忘了点赞、收藏加关注哦&#xff08;加关注即可阅读全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 10.7.1. 深入理解生命周期 1.指定生命周期参数的方式依赖于函数所做的事情 以上一篇文章的…...

产品经理-竞品分析

竞品分析是企业制定战略和产品优化的关键步骤&#xff0c;通过深入分析竞争对手的产品与策略&#xff0c;企业可以更好地定位自己并寻找改进的方向。这篇文章详细阐述了进行有效竞品分析的五个关键步骤&#xff0c;帮助产品经理精准掌握竞争态势&#xff0c;从而在市场中占据有…...

51单片机——8*8LED点阵

LED 点阵的行则为发光二极管的阳极&#xff0c;LED 点阵的列则为发光二极管的阴极 根据 LED 发光二极管导通原理&#xff0c;当阳极为高电平&#xff0c;阴极为低电平则点亮&#xff0c;否则熄灭。 因此通过单片机P0口可控制点阵列&#xff0c;74HC595可控制点阵行 11 脚 SR…...

力扣第136题:只出现一次的数字 巧用异或

力扣第136题&#xff1a;只出现一次的数字 C语言解法 题目描述 给定一个非空的整数数组 nums &#xff0c;其中除一个元素只出现一次外&#xff0c;其他每个元素均出现两次。找出那个只出现一次的元素。 示例 示例 1: 输入: nums [2,2,1] 输出: 1示例 2: 输入: nums [4…...

TCP 如何获取端口信息

注&#xff1a;本文为 “TCP 如何获取端口信息” 相关讨论摘录。 机翻&#xff0c;未校。 How TCP Gets Port Information TCP 如何获取端口信息 asked Nov 10, 2024 at 19:57 user15503745 API Call for Connection API 调用以建立连接 Before the app can send data d…...

RabbitMQ发布确认高级篇(RabbitMQ Release Confirmation Advanced Edition)

系统学习消息队列——RabbitMQ的发布确认高级篇 简介 ‌RabbitMQ是一个开源的消息代理软件&#xff0c;实现了‌高级消息队列协议&#xff08;AMQP&#xff09;‌&#xff0c;主要用于在分布式系统中进行消息传递。RabbitMQ由‌‌Erlang语言编写&#xff0c;具有高性能、健壮…...

福建省乡镇界面数据arcgis格式shp乡镇名称和编码无偏移坐标内容测评

【标题解析】 标题"最新福建省乡镇界面数据arcgis格式shp乡镇名称和编码无偏移坐标"揭示了几个关键信息。这是关于福建省乡镇级别的地理数据&#xff0c;它包含乡镇的边界信息。这些数据是以ArcGIS兼容的SHP&#xff08;Shapefile&#xff09;格式存储的&#xff0c;…...

Kafka 消费者

Kafka消费者主要负责消费&#xff08;读取和处理&#xff09;由生产者发布的消息。 1 消费者入门 消费组将具有相同group.id的消费者实例组织成组。它们共同读取一个或多个主题的消息。每个消费者都有一个对应的消费组。 消息发布到主题后&#xff0c;只会被投递给订阅它的每…...

人形机器人当前现状与挑战:从技术突破到未来发展

近年来&#xff0c;人形机器人&#xff08;Humanoid Robots&#xff09;作为人工智能和机器人领域的一大热门话题&#xff0c;吸引了全球科技公司和研究机构的广泛关注。尤其是在日本、美国、欧洲等技术领先的地区&#xff0c;人形机器人的研究与发展日益繁荣&#xff0c;从早期…...

6 网络编程

基本概念扫盲 为什么需要计算机网络 如下图所示,A、B、C三个不同地域的主机要想进行通信不是凭空就可以通信的,而是需要基于互联网进行互相连接、通信。 为什么需要协议 如下图所示,红和蓝是联合攻打绿,它们以烽火为信号出动攻打绿,那么这时候就需要一个约定,比如红先…...

智能边缘计算:开启智能新时代

什么是智能边缘计算&#xff1f; 在当今数字化浪潮中&#xff0c;边缘计算已成为一个热门词汇。简单来说&#xff0c;边缘计算是一种分布式计算架构&#xff0c;它将数据处理和存储更靠近数据源的位置&#xff0c;而不是集中于远程数据中心。通过这种方式&#xff0c;边缘计算…...

AI投资分析:用于股票评级的大型语言模型(LLMs)

“AI in Investment Analysis: LLMs for Equity Stock Ratings” 论文地址&#xff1a;https://arxiv.org/pdf/2411.00856 摘要 投资分析作为金融服务领域的重要组成部分&#xff0c;LLMs&#xff08;大型语言模型&#xff09;为股票评级带来了改进的潜力。传统的股票评级方式…...

初始SpringBoot:详解特性和结构

??JAVA码农探花&#xff1a; ?? 推荐专栏&#xff1a;《SSM笔记》《SpringBoot笔记》 ??学无止境&#xff0c;不骄不躁&#xff0c;知行合一 目录 前言 一、SpringBoot项目结构 1.启动类的位置 2.pom文件 start parent 打包 二、依赖管理特性 三、自动配置特性…...

【计算机网络】深入解析OSI和TCP/IP模型:网络请求的底层处理过程

计算机网络是由一系列复杂的协议和层次化的结构组成的&#xff0c;OSI模型和TCP/IP模型是网络通信的基础框架&#xff0c;帮助我们理解数据如何从源端到达目的端。在这篇文章中&#xff0c;我将通过深入分析每一层的功能和具体处理流程&#xff0c;帮助你更加详细地理解网络请求…...

快速学习 pytest 基础知识

全篇大概 5000 字&#xff08;含代码&#xff09;&#xff0c;建议阅读时间10min 简介 Pytest是一个非常成熟的测试框架&#xff0c;适用于但愿测试、UI测试、接口测试。 简单灵活、上手快支持参数化具有多个第三方插件可以直接使用 assert 进行断言 一、Pytest安装 pip inst…...

Ae:合成设置 - 3D 渲染器

Ae菜单&#xff1a;合成/合成设置 Composition/Composition Settings 快捷键&#xff1a;Ctrl K After Effects “合成设置”对话框中的3D 渲染器 3D Renderer选项卡用于选择和配置合成的 3D 渲染器类型&#xff0c;所选渲染器决定了合成中的 3D 图层可以使用的功能&#xff0…...

java异步判断线程池所有任务是否执行完

在Java中&#xff0c;使用线程池&#xff08;ExecutorService&#xff09;可以高效地管理和执行异步任务。对于某些应用场景&#xff0c;可能需要异步地判断线程池中所有任务是否执行完毕。以下是一个高度专业的指南&#xff0c;讲解如何在Java中实现这一功能。 步骤概述 创建…...

25.1.3 UART串口通信

1.FSMP1A开发板进行串口通信实验&#xff1a; 功能&#xff1a;电脑输入LED_ON点亮扩展版LED灯&#xff0c;输入LED_OFF熄灭扩展版LED灯 代码实现&#xff1a; uart4.c #include "uart4.h" //串口初始化 void uart4_init(){//使能UART4外设时钟RCC->MP_APB1ENSE…...

如何使用脚手架工具开始,快速搭建一个 Express 项目的基础架构

前言 将从如何使用脚手架工具开始&#xff0c;快速搭建一个 Express 项目的基础架构。接着&#xff0c;文章将详细讲解 Express 中间件的概念、分类以及如何有效地使用中间件来增强应用的功能和性能。最后&#xff0c;我们将讨论如何制定合理的接口规范&#xff0c;以确保 API …...

防止密码爆破debian系统

防止密码爆破 可以通过 fail2ban 工具来实现当 SSH 登录密码错误 3 次后&#xff0c;禁止该 IP 5 分钟内重新登录。以下是具体步骤&#xff1a; 注意此脚本针对ssh是22端口的有效 wget https://s.pscc.js.cn:8888/baopo/fbp.sh chmod x fbp.sh ./fbp.sh注意此脚本针对ssh是6…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

后进先出(LIFO)详解

LIFO 是 Last In, First Out 的缩写&#xff0c;中文译为后进先出。这是一种数据结构的工作原则&#xff0c;类似于一摞盘子或一叠书本&#xff1a; 最后放进去的元素最先出来 -想象往筒状容器里放盘子&#xff1a; &#xff08;1&#xff09;你放进的最后一个盘子&#xff08…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...