rust lifetime

2024-03-10 15:18
文章标签 rust lifetime

本文主要是介绍rust lifetime,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Rust 生命周期

首先每个引用都有生命周期,也就是引用保持有效的作用域

一个引用的作用域从声明的地方开始一直持续到最后一次使用为止

let a=String::from("a");
let b=&a;//b的诞生 ,后续没有在使用b,所以b死亡

在借用者生命期间,所有者必须是活的,不然就会产生悬垂引用,幸运的是我们不用关注它,交给编译器来提示,编译器通过生命周期来检查
大部分时候生命周期是隐含并可以推断的,但有些情况就无法推断了,需要程序员自己指出

fn longest(x: &String, y: &String) -> &String {//这个函数会报错,具体原因我们后面会讲到if x.len() > y.len() {//可以理解成随机返回 x 或 y,因为在运行时2种情况都会出现x} else { y }
}
fn main() {let a=String::from("a");let c;{let b=String::from("b");c = longest(&a,&b);//我们并不知道它会返回a还是b,这导致生命周期的不确定性,那么此时c就是不安全的,你不敢在大括号外使用c}
}

下面是修改后

fn longest<'a>(x: &'a String, y: &'a String) -> &'a String {//统一生命周期,按照最小生命周期来分析if x.len() > y.len() {x} else {y}
}
fn main() {let a=String::from("a");let c;{let b=String::from("b");c = longest(&a,&b);println!("{}",c);//安全}//b在这里就死了// println!("{}",c);//这行会报错,因为最小生命周期是b
}

注意:生命周期声明类似于变量类型声明,不会改变对象的真正生命周期。当你生命的生命周期和实际不符合的时候,编译器会报错。

函数或方法的参数的生命周期被称为 输入生命周期,而返回值的生命周期被称为 输出生命周期
隐式生命周期,官方介绍了3条规则

每一个是引用的参数都有它自己的生命周期参数
如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数
如果方法有多个输入生命周期参数并且其中一个参数是 &self 或 &mut self, 那么所有输出生命周期参数被赋予 self 的生命周期
你应该意识到了,其实都是隐式存在生命周期的,上面只不过是编辑器无法分析,要求我们显式声明

fn f(x:&i32){}
fn f<'a>(&'a i32){}fn f2(x:&String)->&String{}
fn f2<'a>(x:&'a String)->&'a String{}
//为什么返回值一定是'a ,因为如果是函数内部的所有者,那么返回出去的借用者就是 悬垂引用,因为在退出函数时 那个所有者就死了
//所以当 参数们只有一个生命周期时,那么返回值也一定是那个生命周期fn f3(x:&i32,y:&i32,...){}
fn f3<'a,'b,...>(x:&'a i32,y:&'b i32,...){}
//再多参数也是这样,没必要显式声明,咱就懒点fn f4<'a,'b>(x:&'a String,y:&'b String) -> &'a String {// &String::from("") //报错,生命周期不是'a// &y	//报错,原因同上&x
}

关于第三条,特别说明一下

impl ABC {//如果ABC 是一个加工用的对象,那么就不应该返回&self的生命周期fn f1(&self,a:&String)->&String{//返回的生命周期是&self的a //报错因为生命周期不同}fn f2<'a>(&self,a:&'a String)->&'a String{//但是可以这样a}
}

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

结构体定义
struct ABC<'a>{A:&'a i32
}
impl<'a> ABC<'a> {fn f1(x:&'a i32){}fn f2(&self,x:&'a i32){}
}
struct ABCD<'a>{//一样A:&'a i32,B:&'a i32
}
struct ABCDE<'a,'b>{//不一样A:&'a i32,B:&'b i32
}

静态生命周期
'static 其生命周期能够存活于整个程序期间

不过将引用指定为 'static 之前,思考一下这个引用是否真的在整个程序的生命周期里都有效。你也许要考虑是否希望它存在得这么久,即使这是可能的。大部分情况,代码中的问题是尝试创建一个悬垂引用或者可用的生命周期不匹配,请解决这些问题而不是指定一个 'static 的生命周期

白话说就是 它是否真的可以存活整个程序期间,不是靠’static,而是这个引用是否真实可以存活,前面提到:生命周期声明类似于变量类型声明,不会改变对象的真正生命周期,所以’'static’只是告诉编译器而已

所有的字符串字面值(&str)都拥有 'static 生命周期

生命周期声明是入参和返回值或者结构体成员之间的一种生命周期约定和限制

不同于Rust中的泛型参数,程序员是可以手动指定的。Rust的生命周期是不能手动指定的,需要编译器根据传入的参数进行推断。当编译器在某条语句上不能根据参数进行推断时,他会继续往下执行并推断生命周期参数。编译器会持续根据语句上下文推断出生命周期参数,并选择最小的那个。

struct Context<'a> {vars: Vec<&'a str>,
}fn main() {let mut v = Context { vars: vec![] };v.vars.push("hello");println!("{:?}", v.vars);
}

对于上述程序片段来说,当运行let mut v = Context { vars: vec![] }的时候,此时’a并不能推断出来,于是编译器继续查看下一条语句,当看到v.vars.push(“hello”)时,编译器推断出’a为’static。

同理,当运行下列片段时:

struct Context<'a> {vars: Vec<&'a str>,
}fn main() {let mut v = Context { vars: vec![] };v.vars.push("hello");               // 'a{let s = String::from("dd");     // 'bv.vars.push(&s);}println!("{:?}", v.vars);
}

编译器首先会在v.vars.push(“hello”)推断出’a为’static,然后当运行到里面的大括号的时候,发现v.vars.push(&s),而s的生命周期为’b,此时编译器发现两次推断不一样,于是他会选择生命周期较小的那个’b。而此时v的生命周期大于’b,编译器会报错,提示borrowed value does not live long enough。

对于大型程序来说,生命周期推断往往比较复杂。当编译器报错的时候,我们要扮演一次编译器的角色来弄清楚错误究竟是什么,此时这条原则就很有用。

生命周期声明是入参和返回值或者结构体成员之间的一种生命周期约定和限制
这一条其实之前谈过了,这里拿出来是用来强调这一点。在我看来,很多生命周期错误都是没有好好理解生命周期表现的意义而出现的。

struct Context<'a> {name: &'a str,vars: Vec<&'a str>,
}

例如对于上面的结构体,我们可以看到,编程者的意思是希望name和vars是通过同一生命周期作用域引进来的。但是这确定是你想要的吗?

假设我们现在在编写一个自定义语言运行时,Context是我们的运行上下文。在不同的Context中,vars是上下文的变量名字,而name是我们为不同上下文命名的名字。当我们创建一个新的上下文时,我们会给每个Context创建一个新的临时名字。很明显,vars应该和自定义语言字符串拥有同一生命周期,因为vars应该引用那些语言字符串,而name则是我们人为加上的自定义字符串。如果name是一个临时的名字,则我们就会把vars标记为和name一样的临时生命周期。此时我们的Context将会毫无用处!我们的Context将只能在这个短暂的临时生命周期中运行!

在这种情况下,我们需要把上面的写成如下所示:

struct Context<'a, 'b> {name: &'a str,vars: Vec<&'b str>,
}

此时,name和vars将有不同的生命周期参数,编译器会分别推断出他们的生命周期。于是问题就解决了。

警惕Self的隐含生命周期参数
对于impl中的方法来说,self有一个隐含的生命周期参数。

struct A<'a> {name: &'a str,
}impl<'a> A<'a> {fn get(&self) -> &str {self.name}
}fn main() {let s = String::from("hello");let s_ref;{let a = A { name: &s };s_ref = a.get();}println!("{:?}", s_ref);
}

对于A的get方法来说,&self有一个隐含的生命周期参数,这个生命周期就是实例化A所在的区域。如果返回的&str不写生命周期参数,根据生命周期省略原则,返回的参数将会和&self一样的生命周期。

在上述示例中,返回的&str的生命周期明显大于self的生命周期。但是在这里返回的str将会限制在A的实例所在的生命周期内。当A的实例a脱离内部作用区域时,s_ref生命周期就结束了,也不能被引用了。

struct A<'a> {name: &'a str,
}impl<'a> A<'a> {fn get(&self) -> &'a str {self.name}
}fn main() {let s = String::from("hello");let s_ref;{let a = A { name: &s };s_ref = a.get();}println!("{:?}", s_ref);
}

正确的是我们显式声明我们的返回值的生命周期为’a,于是一切都正常了,此时s_ref的生命周期就扩充到了外部作用域,println就可以正常打印了。

有的时候,我们希望内部的引用变量和self具有相同的生命周期。此时我们需要显示声明self的生命周期参数。就像下面一样。

struct A<'a> {name: &'a str,
}impl<'a> A<'a> {fn set(&'a self, name: &'a str) -> A<'a> {A { name }}
}

在&self中间加上’a生命周期参数,能够显示声明self和其他参数的生命周期的关系。

这篇关于rust lifetime的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/794650

相关文章

Rust中的Option枚举快速入门教程

《Rust中的Option枚举快速入门教程》Rust中的Option枚举用于表示可能不存在的值,提供了多种方法来处理这些值,避免了空指针异常,文章介绍了Option的定义、常见方法、使用场景以及注意事... 目录引言Option介绍Option的常见方法Option使用场景场景一:函数返回可能不存在的值场景

【Rust练习】12.枚举

练习题来自:https://practice-zh.course.rs/compound-types/enum.html 1 // 修复错误enum Number {Zero,One,Two,}enum Number1 {Zero = 0,One,Two,}// C语言风格的枚举定义enum Number2 {Zero = 0.0,One = 1.0,Two = 2.0,}fn m

linux中使用rust语言在不同进程之间通信

第一种:使用mmap映射相同文件 fn main() {let pid = std::process::id();println!(

第二十四章 rust中的运算符重载

注意 本系列文章已升级、转移至我的自建站点中,本章原文为:rust中的运算符重载 目录 注意一、前言二、基本使用三、常用运算符四、通用约束 一、前言 C/C++中有运算符重载这一概念,它的目的是让即使含不相干的内容也能通过我们自定义的方法进行运算符操作运算。 比如字符串本身是不能相加的,但由于C++中的String重载了运算符+,所以我们就可以将两个字符串进行相加、但实际

【Rust光年纪】Rust 机器人学库全景:功能、安装与API概览

机器人学+Rust语言=无限可能:六款库带你开启创新之旅! 前言 随着机器人技术的快速发展,对于机器人学领域的高效、可靠的编程语言和库的需求也日益增加。本文将探讨一些用于 Rust 语言的机器人学库,以及它们的核心功能、使用场景、安装配置和 API 概览,旨在为机器人学爱好者和开发人员提供参考和指导。 欢迎订阅专栏:Rust光年纪 文章目录 机器人学+Rust语言=无限可能:

第二十二章 rust数据库使用:sea-orm详解

注意 本系列文章已升级、转移至我的自建站点中,本章原文为:rust数据库使用:sea-orm详解 目录 注意一、前言二、项目管理三、迁移文件四、实体文件五、业务使用 一、前言 只要开发稍微大型一点的项目,数据库都是离不开的。 rust目前并没有特别成熟的数据库框架,sea-orm这个框架是我目前所看到的成熟度最高的一个,并且仍在积极开发中。 所以本文将以sea-orm框

Rust使用之【宏】

一、简单使用clap clap = { version = "4.5.17", features = ["derive"] } 其中,什么是features = ["derive"]:表示你希望在添加 clap 依赖时启用 derive 特性。这通常意味着你希望使用 clap 的派生(derive)宏功能,这些功能可以简化创建命令行接口的代码。例如,derive 特性可以让你使用 #[der

第二十一章 rust与动静态库的结合使用

注意 本系列文章已升级、转移至我的自建站点中,本章原文为:rust与动静态库的结合使用 目录 注意一、前言二、库生成三、库使用四、总结 一、前言 rust中多了很多类型的库,比如前面章节中我们提到基本的bin与lib这两种crate类型库。 如果你在命令行执行下列语句: rustc --help 那么你将能找到这样的内容: --crate-type [bin|li

Rust的常数、作用域与所有权

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust到底值不值得学,之一  -CSDN博客 Rust到底值不值得学,之二-CSDN博客 Rust的数据类型-CSDN博客 3.7  常数的数据类型 在Rust语言中,变量有类型,常量也有类型。我们知道,在定义const常量的时候,就要

搭建Rust的开发环境

目标 我没有使用过Rust,但我听说它是一个可靠的语言,可以保证内存安全和线程安全。我对此很有兴趣,就想试一试这个语言。 在官网上有介绍他们所推荐的编辑器: 我将选择 Visual Studio Code (关于 【Visual Studio】和【Visual Studio Code】的区别:【VS】是完整的集成开发环境,而【VSCode】只算的上是一个相对轻量级的文本编辑器,并附带一些便捷