【Rust练习】23.生命周期

练习题来自https://practice-zh.course.rs/lifetime/basic.html

1

/* 为 `i` 和 `borrow2` 标注合适的生命周期范围 */// `i` 拥有最长的生命周期,因为它的作用域完整的包含了 `borrow1` 和 `borrow2` 。
// 而 `borrow1` 和 `borrow2` 的生命周期并无关联,因为它们的作用域没有重叠
fn main() {let i = 3;                                             {                                                    let borrow1 = &i; // `borrow1` 生命周期开始. ──┐//                                                │println!("borrow1: {}", borrow1); //              │} // `borrow1` 生命周期结束. ──────────────────────────────────┘{                                                    let borrow2 = &i; println!("borrow2: {}", borrow2);               }                                                   
}   

我就不标注了,i的生命周期从main函数开始到结束。

2

/* 像上面的示例一样,为 `r` 和 `x` 标准生命周期,然后从生命周期的角度. */fn main() {  {let r;                // ---------+-- 'a//          |{                     //          |let x = 5;        // -+-- 'b  |r = &x;           //  |       |}                     // -+       |//          |println!("r: {}", r); //          |}                         // ---------+
}

和上面的代码一样,这段代码是无法通过编译的,r的生命周期大于其引用的x。这意味着一旦x被释放,r将成为一个悬垂引用。

3

/* 添加合适的生命周期标注,让下面的代码工作 */
fn longest(x: &str, y: &str) -> &str {if x.len() > y.len() {x} else {y}
}fn main() {}

答案:

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

原则1:每一个引用参数都会获得独特的生命周期
然后没有了,此时编译器无法推导出返回值引用的生命周期,这就需要我们手动标注。因为我们将返回值的生命周期标注为a,就必须让返回值的生命周期小于入参的(否则入参被释放了,就出现悬垂引用的问题了)

另外,类似的生命周期问题在C++中也是存在的:

int &larger(int &a, int &b)
{return a > b ? a : b;
}int main()
{int a = 1;int& res = a;{int b = 2;res = larger(a, b);}cout << res << endl;return 0;
}

注意,C++是强类型语言,因此res的类型必须提前声明(即使用auto,那也得是auto&)。另外,C++中的引用声明时就必须初始化。

当b为更大值时,这里就会出现悬垂引用的问题。但在我编译这段代码并反复运行时,我没有看到异常输出。我的C++水平还没法解释为什么。

4

使用三种方法修复下面的错误

fn invalid_output<'a>() -> &'a String { &String::from("foo") 
}fn main() {
}

第一种当然是最原始的方法,转移所有权:

fn invalid_output() ->  String { String::from("foo") 
}

新增一个入参也是不错的选择,甚至可以省略生命周期标注符号:

fn invalid_output(s: &String) -> &String {s
}

第三种我倒是没想到,看了答案才意识到,我也可以直接返回硬编码的&str

fn invalid_output() -> &'static str { "foo"
}

5

// `print_refs` 有两个引用参数,它们的生命周期 `'a` 和 `'b` 至少得跟函数活得一样久
fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {println!("x is {} and y is {}", x, y);
}/* 让下面的代码工作 */
fn failed_borrow<'a>() {let _x = 12;// ERROR: `_x` 活得不够久does not live long enoughlet y: &'a i32 = &_x;// 在函数内使用 `'a` 将会报错,原因是 `&_x` 的生命周期显然比 `'a` 要小// 你不能将一个小的生命周期强转成大的
}fn main() {let (four, nine) = (4, 9);print_refs(&four, &nine);// 这里,four 和 nice 的生命周期必须要比函数 print_refs 长failed_borrow();// `failed_borrow`  没有传入任何引用去限制生命周期 `'a`,因此,此时的 `'a` 生命周期是没有任何限制的,它默认是 `'static`
}

Rust处理这类问题的一个基本原则是:调用者的生命周期一定要比被调用的短,否则就会出现空的调用。

fn failed_borrow<'a>() {let _x = 12;// ERROR: `_x` 活得不够久does not live long enoughlet y: & i32 = &_x;// 在函数内使用 `'a` 将会报错,原因是 `&_x` 的生命周期显然比 `'a` 要小// 你不能将一个小的生命周期强转成大的
}

6

/* 增加合适的生命周期标准,让代码工作 */// `i32` 的引用必须比 `Borrowed` 活得更久
#[derive(Debug)]
struct Borrowed(&i32);// 类似的,下面两个引用也必须比结构体 `NamedBorrowed` 活得更久
#[derive(Debug)]
struct NamedBorrowed {x: &i32,y: &i32,
}#[derive(Debug)]
enum Either {Num(i32),Ref(&i32),
}fn main() {let x = 18;let y = 15;let single = Borrowed(&x);let double = NamedBorrowed { x: &x, y: &y };let reference = Either::Ref(&x);let number    = Either::Num(y);println!("x is borrowed in {:?}", single);println!("x and y are borrowed in {:?}", double);println!("x is borrowed in {:?}", reference);println!("y is *not* borrowed in {:?}", number);
}

我得去查查Rust有没有什么编码规范,规定了生命周期的命名。

#[derive(Debug)]
struct Borrowed<'a>(&'a i32);// 类似的,下面两个引用也必须比结构体 `NamedBorrowed` 活得更久
#[derive(Debug)]
struct NamedBorrowed<'b> {x: &'b i32,y: &'b i32,
}#[derive(Debug)]
enum Either <'c>{Num(i32),Ref(&'c i32),
}

7

/* 让代码工作 */#[derive(Debug)]
struct NoCopyType {}#[derive(Debug)]
struct Example<'a, 'b> {a: &'a u32,b: &'b NoCopyType
}fn main()
{ let var_a = 35;let example: Example;{let var_b = NoCopyType {};/* 修复错误 */example = Example { a: &var_a, b: &var_b };}println!("(Success!) {:?}", example);
}

b的生命周期太短了,打印exampleb已经被释放了。

fn main()
{ let var_a = 35;let example: Example;let var_b = NoCopyType {};{/* 修复错误 */example = Example { a: &var_a, b: &var_b };}println!("(Success!) {:?}", example);
}

8


#[derive(Debug)]
struct NoCopyType {}#[derive(Debug)]
#[allow(dead_code)]
struct Example<'a, 'b> {a: &'a u32,b: &'b NoCopyType
}/* 修复函数的签名 */
fn fix_me(foo: &Example) -> &NoCopyType
{ foo.b }fn main()
{let no_copy = NoCopyType {};let example = Example { a: &1, b: &no_copy };fix_me(&example);println!("Success!")
}

加个生命周期标识符就行了,至于是否真能活到那时候,谁知道。

fn fix_me<'a>(foo: &'a Example) -> &'a NoCopyType
{ foo.b }

9

/* 添加合适的生命周期让下面代码工作 */
struct ImportantExcerpt {part: &str,
}impl ImportantExcerpt {fn level(&'a self) -> i32 {3}
}fn main() {}

答案

struct ImportantExcerpt<'a> {part: &'a str,
}impl<'a> ImportantExcerpt<'a> {fn level(&'a self) -> i32 {3}
}

10

/* 移除所有可以消除的生命周期标注 */fn nput<'a>(x: &'a i32) {println!("`annotated_input`: {}", x);
}fn pass<'a>(x: &'a i32) -> &'a i32 { x }fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {x
}struct Owner(i32);impl Owner {fn add_one<'a>(&'a mut self) { self.0 += 1; }fn print<'a>(&'a self) {println!("`print`: {}", self.0);}
}struct Person<'a> {age: u8,name: &'a str,
}enum Either<'a> {Num(i32),Ref(&'a i32),
}fn main() {}

由于入参只有一个引用,引用只有一个生命周期,第一个、第二个消除:

fn nput(x: &i32) {println!("`annotated_input`: {}", x);
}fn pass(x: &i32) -> &i32 { x }

参数含有self时(即方法),self的生命周期会赋给全部输出,这里也消除:

struct Owner(i32);impl Owner {fn add_one(&mut self) { self.0 += 1; }fn print(&self) {println!("`print`: {}", self.0);}
}

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

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

相关文章

conda、pip同时安装包引起混乱问题剖析

一句话总结&#xff1a; 安装版本不一致时会有两个.dist-info文件夹&#xff08;举例&#xff1a;scapy-2.6.1.dist-info和scapy-2.4.3.dist-info&#xff09;&#xff0c;conda list和pip list依靠这两个文件夹进行包的识别&#xff08;疑似pip list识别老版本&#xff0c;co…

类和对象--中--运算符重载、日期类实现(重要)

1.运算符重载 2.1作用&#xff1a; 为了让C的新类型&#xff1a;类。也可以进行内置类型的运算符操作。所以就有了运算符重载。 2.2定义&#xff1a; 运算符重载是具有特殊名字的函数&#xff0c;他的名字是由operator和后⾯要定义的运算符共同构成。和其 他函数⼀样&#…

Linux基本指令【Linux系统】

文章目录 lspwdcdtouchmkdirrmdirrmmancpmvcatlessheadtailfindwhichwhereisaliasgrepzip和unzipunameexitshutdown ls 显示指定[指定方式 &#xff1a;ls空格路径]路径中非隐藏的文件 使用语法&#xff1a; ls [选项] [⽬录或⽂件] ls的常用选项 -a列出目录下的所有文件,包…

[开源]3K+ star!微软Office的平替工具,跨平台,超赞!

大家好&#xff0c;我是JavaCodexPro&#xff01; 数字化的当下&#xff0c;高效的办公工具是提升工作效率的关键&#xff0c;然而大家想到的一定是 Microsoft Office 办公软件&#xff0c;然而价格也是相当具有贵的性价比。 今天JavaCodexPro给大家分享一款超棒的开源办公套…

C++ 中的随机数生成全方面介绍

目录 引言 基本概念 什么是随机数&#xff1f; 伪随机数生成器&#xff08;PRNG&#xff09; 线性同余生成器&#xff08;LCG&#xff09; 梅尔森旋转算法&#xff08;Mersenne Twister&#xff09; 随机数的质量 随机数生成器的测试 C 中的随机数生成 传统方法&…

Django实现智能问答助手-数据库方式读取问题和答案

扩展 增加问答数据库&#xff0c;通过 Django Admin 添加问题和答案。实现更复杂的问答逻辑&#xff0c;比如使用自然语言处理&#xff08;NLP&#xff09;库。使用前端框架&#xff08;如 Bootstrap&#xff09;增强用户界面 1.注册模型到 Django Admin&#xff08;admin.py…

问:Spring Boot应用监控组件工具,梳理一下?

在日常运维与开发过程中&#xff0c;Spring Boot 应用的监控是确保系统稳定性和性能的关键环节。本文将探讨 Spring Boot 常用的监控组件及工具的原理、适用场景&#xff0c;并针对不同场景下的运维监控方案进行介绍。 1. Spring Boot Actuator 原理&#xff1a; Spring Boo…

Spring-02-springmvc

2. 什么是SpringMVC 2.1. 概述 Spring MVC是Spring Framework的一部分&#xff0c;是基于Java实现MVC的轻量级Web框架。 为什么要学习SpringMVC呢? Spring MVC的特点&#xff1a; 轻量级&#xff0c;简单易学高效 , 基于请求响应的MVC框架与Spring兼容性好&#xff0c;无缝…

【数据结构】【线性表】一文讲完队列(附C语言源码)

队列 队列的基本概念基本术语基本操作 队列的顺序实现顺序队列结构体的创建顺序队列的初始化顺序队列入队顺序队列出队顺序队列存在的问题分析循环队列代码汇总 队列的链式实现链式队列的创建链式队列初始化-不带头结点链式队列入队-不带头节点链式队列出队-不带头结点带头结点…

cf 988 div3 C 将一些特定的元素拿出来考虑 E(交互,考虑多询问一位,以此判断这一位的情况)

C题意&#xff1a; [1 n] 的排列&#xff0c;我们要任意相邻的数相加得到的和是合数。 如果对于n 不存在这个一个排列&#xff0c;那么输出-1 一个比较显然的思路 就是奇数放一起&#xff0c;偶数放一起。奇数之间相加是偶数 偶数之间相加是偶数。那么他们都是合数。 但是还有…

手机文件可以打印出来吗

在数字化时代&#xff0c;手机已成为我们日常生活和工作中不可或缺的一部分。很多时候&#xff0c;我们需要将手机上的文件打印出来&#xff0c;无论是学习资料、工作报告还是生活文档。那么&#xff0c;手机上的文件真的可以打印出来吗&#xff1f;答案是肯定的。 直接前往打…

《Spring Boot:快速构建应用的利器》

一、Spring Boot 的崛起与优势 &#xff08;四&#xff09;丰富的生态支持 Spring Boot 拥有强大的生态系统&#xff0c;这是它在 Java 开发领域中占据重要地位的关键因素之一。 Spring Cloud 是 Spring Boot 生态中的重要组成部分&#xff0c;它为构建分布式系统的微服务架构…

爬虫实战:采集知乎XXX话题数据

目录 反爬虫的本意和其带来的挑战目标实战开发准备代码开发发现问题1. 发现问题[01]2. 发现问题[02] 解决问题1. 解决问题[01]2. 解决问题[02] 最终结果 结语 反爬虫的本意和其带来的挑战 在这个数字化时代社交媒体已经成为人们表达观点的重要渠道&#xff0c;对企业来说&…

微信小程序2-地图显示和地图标记

一、index修改页面&#xff0c;让页面能够显示地图和一个添加标记的按钮。 index.wxml <scroll-view class"scrollarea" scroll-y type"list"><view class"index_container"><map id"map" style"width: 100%; h…

Qt入门1——认识Qt的几个常用头文件和常用函数

1.头文件 ① #include <QPushButton>——“按钮”头文件&#xff1b; ② #include <QLabel>——“标签”头文件&#xff1b; ③ #include <QFont>——“字体”头文件&#xff1b; ④#include <QDebug>——输出相关信息&#xff1b; 2. 常用函数/类的基…

【vue3+Typescript】unapp+stompsj模式下替代plus-websocket的封装模块

由于plus-websocket实测存在消息丢失的问题&#xff0c;只能寻找替代的方案&#xff0c;看文章说使用原生的即可很好的工作。而目前在stompjs里需要使用websocket类型的封装模块&#xff0c;看了下原来提供的接口&#xff0c;采用uniapp原生的websocket模式&#xff0c;对原模块…

社交电商专业赋能高校教育与产业协同发展:定制开发AI智能名片及2+1链动商城小程序的创新驱动

摘要&#xff1a;本文围绕社交电商有望成为高校常态专业这一趋势展开深入探讨&#xff0c;剖析国家政策认可下其学科发展前景&#xff0c;着重阐述在专业建设进程中面临的师资短缺及实践教学难题。通过引入定制开发AI智能名片与21链动商城小程序&#xff0c;探究如何借助这些新…

Linux进阶:环境变量

环境变量是一组信息记录&#xff0c;类型是KeyValue型&#xff08;名值&#xff09;&#xff0c;用于操作系统运行的时候记录关键信息. env命令&#xff1a;查看系统全部的环境变量 语法&#xff1a;env $符号&#xff1a;取出指定的环境变量的值 语法&#xff1a;$变量名 …

CPU命名那些事

一、Intel CPU命名 1. 命名结构 Intel CPU 的命名通常包含以下几个部分&#xff1a; 品牌 产品线 系列 代数 具体型号 后缀 例如&#xff1a;Intel Core i7-13700K 2. 各部分含义 品牌 Intel&#xff1a;表示厂商&#xff08;几乎所有命名中都有&#xff09;。不同品…

英语写作中“联系、关联”associate correlate 及associated的用法

似乎是同义词的associate correlate 实际上意思差别明显&#xff0c;associate 是人们把两者联系在一起&#xff08;主观联系&#xff09;&#xff0c;而correlate 指客观联系。 例如&#xff1a; We always associate sports with health.&#xff08;我们总是将运动和健康联…