rust 生命周期2
之前定义的结构体,都是不含引用的。 如果想定义含引用的结构体,请定义生命周期注解
#[warn(unused_variables)]
struct ImportantExcerpt<'a> {
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 '.'");
let i = ImportantExcerpt { part: first_sentence };
}
这个结构体有一个字段,part
,它存放了一个字符串 slice,这是一个引用。类似于泛型参数类型,必须在结构体名称后面的尖括号中声明泛型生命周期参数,以便在结构体定义中使用生命周期参数。这个注解意味着 ImportantExcerpt
的实例不能比其 part
字段中的引用存在的更久。
这里的 main
函数创建了一个 ImportantExcerpt
的实例,它存放了变量 novel
所拥有的 String
的第一个句子的引用。novel
的数据在 ImportantExcerpt
实例创建之前就存在。另外,直到 ImportantExcerpt
离开作用域之后 novel
都不会离开作用域,所以 ImportantExcerpt
实例中的引用是有效的
生命周期省略(Lifetime Elision)
现在我们已经知道了每一个引用都有一个生命周期,而且我们需要为那些使用了引用的函数或结构体指定生命周期。
有一种情况,它没有生命周期注解却能编译成功:
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[..]
}
定义了一个没有使用生命周期注解的函数,即便其参数和返回值都是引用
这个函数没有生命周期注解却能编译是由于一些历史原因:在早期版本(pre-1.0)的 Rust 中,这的确是不能编译的。
每一个引用都必须有明确的生命周期。那时的函数签名将会写成这样:
fn first_word<'a>(s: &'a str) -> &'a str {
在编写了很多 Rust 代码后,Rust 团队发现在特定情况下 Rust 程序员们总是重复地编写一模一样的生命周期注解。这些场景是可预测的并且遵循几个明确的模式。接着 Rust 团队就把这些模式编码进了 Rust 编译器中,如此借用检查器在这些情况下就能推断出生命周期而不再强制程序员显式的增加注解。
这里我们提到一些 Rust 的历史是因为更多的明确的模式被合并和添加到编译器中是完全可能的。未来只会需要更少的生命周期注解。
被编码进 Rust 引用分析的模式被称为 生命周期省略规则(lifetime elision rules)。这并不是需要程序员遵守的规则;这些规则是一系列特定的场景,此时编译器会考虑,如果代码符合这些场景,就无需明确指定生命周期。
省略规则并不提供完整的推断:如果 Rust 在明确遵守这些规则的前提下变量的生命周期仍然是模棱两可的话,它不会猜测剩余引用的生命周期应该是什么。在这种情况,编译器会给出一个错误,这可以通过增加对应引用之间相联系的生命周期注解来解决。
函数或方法的参数的生命周期被称为 输入生命周期(input lifetimes),而返回值的生命周期被称为 输出生命周期(output lifetimes)。
编译器采用三条规则来判断引用何时不需要明确的注解。第一条规则适用于输入生命周期,后两条规则适用于输出生命周期。如果编译器检查完这三条规则后仍然存在没有计算出生命周期的引用,编译器将会停止并生成错误。这些规则适用于 fn
定义,以及 impl
块。
第一条规则是每一个是引用的参数都有它自己的生命周期参数。换句话说就是,有一个引用参数的函数有一个生命周期参数:fn foo<'a>(x: &'a i32)
,有两个引用参数的函数有两个不同的生命周期参数,fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
,依此类推。
第二条规则是如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:fn foo<'a>(x: &'a i32) -> &'a i32
。
第三条规则是如果方法有多个输入生命周期参数,不过其中之一因为方法的缘故为 &self
或 &mut self
,那么 self
的生命周期被赋给所有输出生命周期参数。第三条规则使得方法更容易读写,因为只需更少的符号。
假设我们自己就是编译器。并应用这些规则来计算 first_word
函数签名中的引用的生命周期。开始时签名中的引用并没有关联任何生命周期:
fn first_word(s: &str) -> &str {
接着编译器应用第一条规则,也就是每个引用参数都有其自己的生命周期。我们像往常一样称之为 'a
,所以现在签名看起来像这样:
fn first_word<'a>(s: &'a str) -> &str {
对于第二条规则,因为这里正好只有一个输入生命周期参数所以是适用的。第二条规则表明输入参数的生命周期将被赋予输出生命周期参数,所以现在签名看起来像这样:
fn first_word<'a>(s: &'a str) -> &'a str {
现在这个函数签名中的所有引用都有了生命周期,如此编译器可以继续它的分析而无须程序员标记这个函数签名中的生命周期。
让我们再看看另一个例子,这次我们从示例(上一篇博客, 函数-生命周期)中没有生命周期参数的 longest
函数开始:
fn longest(x: &str, y: &str) -> &str {
再次假设我们自己就是编译器并应用第一条规则:每个引用参数都有其自己的生命周期。这次有两个参数,所以就有两个(不同的)生命周期:
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
再来应用第二条规则,因为函数存在多个输入生命周期,它并不适用于这种情况。再来看第三条规则,它同样也不适用,这是因为没有 self
参数。应用了三个规则之后编译器还没有计算出返回值类型的生命周期。这就是为什么在编译示例 longest 的代码时会出现错误的原因:编译器使用所有已知的生命周期省略规则,仍不能计算出签名中所有引用的生命周期。
因为第三条规则真正能够适用的就只有方法签名,现在就让我们看看那种情况中的生命周期,并看看为什么这条规则意味着我们经常不需要在方法签名中标注生命周期。
方法定义中的生命周期注解
当为带有生命周期的结构体实现方法时,其语法依然类似示例中展示的泛型类型参数的语法。声明和使用生命周期参数的位置依赖于生命周期参数是否同结构体字段或方法参数和返回值相关。
(实现方法时)结构体字段的生命周期必须总是在 impl
关键字之后声明并在结构体名称之后被使用,因为这些生命周期是结构体类型的一部分。
impl
块里的方法签名中,引用可能与结构体字段中的引用相关联,也可能是独立的。另外,生命周期省略规则也经常让我们无需在方法签名中使用生命周期注解。让我们看看一些使用示例中定义的结构体 ImportantExcerpt
的例子。
首先,这里有一个方法 level
。其唯一的参数是 self
的引用,而且返回值只是一个 i32
,并不引用任何值:
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
impl
之后和类型名称之后的生命周期参数是必要的,不过因为第一条生命周期规则我们并不必须标注 self
引用的生命周期。
这里是一个适用于第三条生命周期省略规则的例子:
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
println!("Attention please: {}", announcement);
self.part
}
}
这里有两个输入生命周期,所以 Rust 应用第一条生命周期省略规则并给予 &self
和 announcement
他们各自的生命周期。接着,因为其中一个参数是 &self
,返回值类型被赋予了 &self
的生命周期,这样所有的生命周期都被计算出来了。
相当于:
fn announce_and_return_part<'a,'b>(&'a self, announcement: &'b str) -> &'a str {
总结:结构体实例的生命周期不能比引用类型的成员字段要大。 如果真的出现这种情况,就会导致,结构体实例还在,但是结构无内容,这种情况也是垂悬引用一种,在rust中是不允许的。
粗鲁的解释: 你他娘的(结构体)都是一个空壳子了,要你有何意义,你的存在还有啥意义?
静态生命周期
这里有一种特殊的生命周期值得讨论:'static
,其生命周期能够存活于整个程序期间。所有的字符串字面值都拥有 'static
生命周期,我们也可以选择像下面这样标注出来:
let s: &'static str = "I have a static lifetime.";
这个字符串的文本被直接储存在程序的二进制文件中而这个文件总是可用的。因此所有的字符串字面值都是 'static
的。
你可能在错误信息的帮助文本中见过使用 'static
生命周期的建议,不过将引用指定为 'static
之前,思考一下这个引用是否真的在整个程序的生命周期里都有效。你可能会考虑希望它一直有效,如果可能的话。大部分情况,代码中的问题是尝试创建一个悬垂引用或者可用的生命周期不匹配,请解决这些问题而不是指定一个 'static
的生命周期。
结合泛型类型参数、trait bounds 和生命周期
让我们简要的看一下在同一函数中指定泛型类型参数、trait bounds 和生命周期的语法!
use std::fmt::Display; 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
}
}
这个是longest示例中那个返回两个字符串 slice 中较长者的 longest
函数,不过带有一个额外的参数 ann
。ann
的类型是泛型 T
,它可以被放入任何实现了 where
从句中指定的 Display
trait 的类型。这个额外的参数会在函数比较字符串 slice 的长度之前被打印出来,这也就是为什么 Display
特质约束是必须的。因为生命周期也是泛型,所以生命周期参数 'a
和泛型类型参数 T
都位于函数名后的同一尖括号列表中。
rust 生命周期2的更多相关文章
- Rust生命周期bound用于泛型的引用
在实际编程中,可能会出现泛型引用这种情况,我们会编写如下的代码: struct Inner<'a, T> { data: &'a T, } 会产生编译错误: error[E0309 ...
- rust 函数-生命周期
记录一下自己理解的生命周期. 每个变量都有自己的生命周期. 在c++里生命周期好比作用域, 小的作用域的可以使用大作用域的变量. 如果把这里的每个作用域取个名,那么就相当于rust里的生命周期注解. ...
- 【译】深入理解Rust中的生命周期
原文标题:Understanding Rust Lifetimes 原文链接:https://medium.com/nearprotocol/understanding-rust-lifetimes- ...
- 对Rust所有权、借用及生命周期的理解
Rust的内存管理中涉及所有权.借用与生命周期这三个概念,下面是个人的一点粗浅理解. 一.从内存安全的角度理解Rust中的所有权.借用.生命周期 要理解这三个概念,你首要想的是这么做的出发点是什么-- ...
- 文盘Rust -- struct 中的生命周期
最近在用rust 写一个redis的数据校验工具.redis-rs中具备 redis::ConnectionLike trait,借助它可以较好的来抽象校验过程.在开发中,不免要定义struct 中的 ...
- [易学易懂系列|rustlang语言|零基础|快速入门|(5)|生命周期Lifetime]
[易学易懂系列|rustlang语言|零基础|快速入门|(5)] Lifetimes 我们继续谈谈生命周期(lifttime),我们还是拿代码来说话: fn main() { let mut a = ...
- react组件的生命周期
写在前面: 阅读了多遍文章之后,自己总结了一个.一遍加强记忆,和日后回顾. 一.实例化(初始化) var Button = React.createClass({ getInitialState: f ...
- 浅谈 Fragment 生命周期
版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...
- C# MVC 5 - 生命周期(应用程序生命周期&请求生命周期)
本文是根据网上的文章总结的. 1.介绍 本文讨论ASP.Net MVC框架MVC的请求生命周期. MVC有两个生命周期,一为应用程序生命周期,二为请求生命周期. 2.应用程序生命周期 应用程序生命周期 ...
随机推荐
- Meta标签基本使用
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><t ...
- centos7 安装rpm版的mysql遇到坑——误删root用户的恢复
在网上找了教程http://blog.csdn.net/frankcheng5143/article/details/77609093安装过程很顺利,随着修改了root的密码后不下心误删了root账号 ...
- zookeeper启动报错:Error contacting service. It is probably not running
Error contacting service. It is probably not running. 今天启动zookeeper集群时,正常启动,但是查看状态时发现,Error contacti ...
- ngnix随笔一
nginx安装及启动 1.用yum源安装,首先配置yum源 在“/etc/yum.repo.d/”下创建yum源文件 例如:cd /etc/yum.repo.d/nginx.repo [nginx-s ...
- JAVASE(十)面向对象:特性之多态性、Object类、代码块、关键字:static、final、父子类执行顺序
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 1.面向对象的特性之:多态性 多态性的理解:事物的多种形态 1.1 广义上多态性的体现:①方法的重写,重 ...
- 【大厂面试03期】MySQL是怎么解决幻读问题的?
问题分析 首先幻读是什么? 根据MySQL文档上面的定义 The so-called phantom problem occurs within a transaction when the same ...
- Java 第十一届 蓝桥杯 省模拟赛 计算机存储中有多少字节
计算机存储中有多少字节 题目 问题描述 在计算机存储中,12.5MB是多少字节? 答案提交 这是一道结果填空的题,你只需要算出结果后提交即可.本题的结果为一个整数,在提交答案时只填写这个整数,填写多余 ...
- Java实现 LeetCode 695 岛屿的最大面积(DFS)
695. 岛屿的最大面积 给定一个包含了一些 0 和 1 的非空二维数组 grid . 一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相 ...
- Java实现基础练习十进制转十六进制
基础练习 十进制转十六进制 时间限制:1.0s 内存限制:512.0MB 提交此题 锦囊1 锦囊2 问题描述 十六进制数是在程序设计时经常要使用到的一种整数的表示方式.它有0,1,2,3,4,5,6, ...
- HttpClientFactory-向外请求的最佳
简介 它的组件包是Microsoft.Extensions.Http 复原HttpClient带来的问题 HttpClient相关问题 虽然HttpClient类实现了IDisposable,但不是首 ...