记录一下自己理解的生命周期。

每个变量都有自己的生命周期。

在c++里生命周期好比作用域, 小的作用域的可以使用大作用域的变量。 如果把这里的每个作用域取个名,那么就相当于rust里的生命周期注解。

拿例子说事一:

如果按照c++的方式来理解, 这个x和r的作用域是一样的,都是在main函数中。

但是如果按照rust生命周期去理解, x的生命周期比r的生命周期大。 因为a在main的第一行. b的在main中的第二行。

有变量的生命周期是一样的,那就是元组结构. 类似这样。  let (a,b,c) = tuple;

rust不允许, let a=1,b=1; 这样定义变量(c/c++/js风格)

也不允许  let a,b=4,1; 这样定义变量, (lua风格)

在这里其实'a 和 'b的生命周期 在编译器看来是不一样的,这一点一定要记住。  但是可以调用同一生命周期的函数,因为 'b生命周期包含'a 生命周期。

编译器处理其实是按照'a生命周期(公共的部分,也可以理解为使用较短生命周期)代入函数中的。  某个变量的生命周期是开始定义变量开始,到最后一次使用变量。   这里可能设计变量租借延续生命周期。

拿例子说事二:

fn main() {
let string1 = String::from("abcd");
let string2 = "xyz"; let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
} fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}

如果用c++的视角(编码风格)去看这个程序, 会觉得是一段很正常的代码,完全没毛病啊。

但是实际编译就会出错,错误提示就是和生命周期有关。

对技术这么严谨的我,内心问了一万个为什么?

我经常把自己想成编译器作者,自己问自己,如果你是作者你会怎么处理?

答: 每个函数都会检查每个变量的生命周期的范围。   对longest函数来说, 有两个参数,x,y  都是都是引用。 本身不占空间(4或8字节指针忽略)。

在编译longest的时候,x,y引用谁,编译器不清楚。 有一种办法是可以知道的,就是编译完整个rust项目代码,在回过头看这个。就可以知道了。

但是这种不现实,这种会造成编译器及其复杂,并且及其缓慢。 因为一个项目不只有只有一个loggest,  有成千上万类似这样的,互相交错调用。

得来回反复的编译检查才能确定引用的谁,生命周期如何,这种是不确定的,x可能引用的东东生命周期长,可能短, 如果把函数结果赋值给属于其他生命周期的变量,就可能存在安全隐患了,c++是可以这么干的,但是开发者必须清楚指针指向的哪,引用的谁,否则程序崩了,不要怪谁,就是开发者垃圾。但是事实上,再牛逼的程序员,也会写出有bug的程序。

rust就是为安全而生的, 编译器帮你排除内存隐患,并发隐患。完成这个目标可不容易,需要开发者协助完成( 导致学习难度有点大,主要卡在思维上。 开发者不要随心所欲)

说了这么多,这个核心问题就是:

粗鲁点解释: 老子(rustc)不知道你指向的东西,和你的函数结果要返回给哪个,不告诉我,老子我就不让你过。

专业点解释: 因为rustc不知道 x 和 y 的生命周期是如何与返回值的生命周期相关联的

开发者独白: 老子该怎么告诉你(rustc)啊?

答:增加泛型生命周期参数来定义引用间的关系以便借用检查器可以进行分析

生命周期注解语法

生命周期注解并不改变任何引用的生命周期的长短。与当函数签名中指定了泛型类型参数后就可以接受任何类型一样,当指定了泛型生命周期后函数也能接受任何生命周期的引用。生命周期注解描述了多个引用生命周期相互的关系,而不影响其生命周期。

生命周期注解有着一个不太常见的语法:生命周期参数名称必须以撇号(')开头,其名称通常全是小写,类似于泛型其名称非常短。'a 是大多数人默认使用的名称。生命周期参数注解位于引用的 & 之后,并有一个空格来将引用类型与生命周期注解分隔开。

这里有一些例子:我们有一个没有生命周期参数的 i32 的引用,一个有叫做 'a 的生命周期参数的 i32 的引用,和一个生命周期也是 'a 的 i32 的可变引用:

&i32        // 引用
&'a i32 // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用

单个的生命周期注解本身没有多少意义,因为生命周期注解告诉 Rust 多个引用的泛型生命周期参数如何相互联系的。例如如果函数有一个生命周期 'a 的 i32 的引用的参数 first。还有另一个同样是生命周期 'a 的 i32 的引用的参数 second。这两个生命周期注解意味着引用 first 和 second 必须与这泛型生命周期存在得一样久。

注解只是一个名字,用单引号和标识符组成.

函数签名中的生命周期注解

就像泛型类型参数,泛型生命周期参数需要声明在函数名和参数列表间的尖括号中。

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

现在函数签名表明对于某些生命周期 'a,函数会获取两个参数,他们都是与生命周期 'a 存在的一样长的字符串 slice。函数会返回一个同样也与生命周期 'a 存在的一样长的字符串 slice。这就是我们告诉 Rust 需要其保证的契约。记住通过在函数签名中指定生命周期参数时,我们并没有改变任何传入后返回的值的生命周期。而是指出任何不遵守这个协议的传入值都将被借用检查器拒绝。注意 longest 函数并不需要知道 x 和 y 具体会存在多久,而只需要知道有某个可以被 'a 替代的作用域将会满足这个签名。

当在函数中使用生命周期注解时,这些注解出现在函数签名中,而不存在于函数体中的任何代码中。这是因为 Rust 能够分析函数中代码而不需要任何协助,不过当函数引用或被函数之外的代码引用时,让 Rust 自身分析出参数或返回值的生命周期几乎是不可能的。这些生命周期在每次函数被调用时都可能不同。这也就是为什么我们需要手动标记生命周期。

当具体的引用被传递给 longest 时,被 'a 所替代的具体生命周期是 x 的作用域与 y 的作用域相重叠的那一部分。换一种说法就是泛型生命周期 'a 的具体生命周期等同于 x 和 y 的生命周期中较小的那一个。因为我们用相同的生命周期参数 'a 标注了返回的引用值,所以返回的引用值就能保证在 x 和 y 中较短的那个生命周期结束之前保持有效。

以下代码正确:

fn main() {
let string1 = String::from("long string is long"); {
let string2 = String::from("xyz");
let result = longest(string1.as_str(), string2.as_str());
println!("The longest string is {}", result);
}
} fn longest<'a>(x:&'a str, y:&'a str) -> &'a str{
if x.len()>y.len() {
x
}else{
y
}
}

以下代码错误:

fn main() {
let string1 = String::from("long string is long");
let result;
{
let string2 = String::from("xyz");
result = longest(string1.as_str(), string2.as_str());
}
println!("The longest string is {}", result);
} fn longest<'a>(x:&'a str, y:&'a str) -> &'a str{
if x.len()>y.len() {
x
}else{
y
}
}

失败原因:

通过生命周期参数告诉 Rust 的是: longest 函数返回的引用的生命周期应该与传入参数的生命周期中较短那个保持一致。

因此,借用检查器不允许示例 10-24 中的代码,因为它可能会存在无效的引用。

longest函数结果的表明的生命周期应该与string2一致。 但是代码反馈的是result与string1一致。

可以使用租借(给string2续命,不要立即释放),修复错误:

fn main() {
let string1 = String::from("long string is long");
let result;
let zujie;
{
let string2 = String::from("xyz");
zujie = string2;
result = longest(string1.as_str(), zujie.as_str());
}
println!("The longest string is {}", result);
}

 

如果返回值和y没有关系,可以不用对y注解。

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

 

出现垂悬引用,代码不通过。 说明:这个可以通过使用有所有权的变量租借解决此问题。说白点,就是给返回值续命。 如果真这样解决,就不需要生命周期注解了。

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

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

rust 函数-生命周期的更多相关文章

  1. React 函数生命周期

      React 函数生命周期基础 1 ,概念 在组件创建.到加载到页面上运行.以及组件被销毁的过程中,总是伴随着各种各样的事件,这些在组件特定时期,触发的事件,统称为组件的生命周期:* 2,组件生命周 ...

  2. 局部变量&&malloc函数&&生命周期的一些见解

    最近在温习指针的部分时发现了一个有趣的问题,先看以下程序: //1.c #include<stdio.h> int* fun() { int t = 567; return &t; ...

  3. Vue钩子函数生命周期实例详解

    vue生命周期简介 Vue实例有一个完整的生命周期,也就是从开始创建.初始化数据.编译模板.挂载Dom.渲染→更新→渲染.卸载等一系列过程,我们称这是Vue的生命周期.通俗说就是Vue实例从创建到销毁 ...

  4. Vue系列之 => 钩子函数生命周期

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. Unity 脚本函数生命周期

    Awake(),一般我们在这里做一些组件的获得,比如使用getcomponent方法. Start(),我们可以在这里给变量赋值. FixUpdate(),固定更新,因为这里得更新速度为固定(可以在T ...

  6. 【vue】钩子函数生命周期

    图1 图2: 图3 相关资料:http://www.zhimengzhe.com/Javascriptjiaocheng/236707.html    https://segmentfault.com ...

  7. rust 生命周期2

    之前定义的结构体,都是不含引用的. 如果想定义含引用的结构体,请定义生命周期注解 #[warn(unused_variables)] struct ImportantExcerpt<'a> ...

  8. 文盘Rust -- struct 中的生命周期

    最近在用rust 写一个redis的数据校验工具.redis-rs中具备 redis::ConnectionLike trait,借助它可以较好的来抽象校验过程.在开发中,不免要定义struct 中的 ...

  9. Unity脚本中各函数成员的生命周期

    在学习Unity时,掌握如何编写脚本是必须掌握的一项基本技能.但是关于Unity的游戏脚本中各函数的生命周期是怎样开始和结束的,它们的执行顺序是如何安排的?这一点我们要清楚的了解. 我们知道Unity ...

随机推荐

  1. 替换Java WEB工程文件的指定字符串

    package com.utils; import java.io.BufferedReader;import java.io.File;import java.io.FileFilter;impor ...

  2. Qt版本中国象棋开发(一)

    开发目的:实现象棋人机对战简单AI,网络对战,移植到android中. 开发平台:windows10 + Qt5.4 for android 开发语言:C++ 开发过程:1.棋盘绘制: 方法一:重写  ...

  3. [自动化-脚本]001.自动领淘金币:Anyweb模拟操作

    通过模拟手工操作的方法领取淘金币.该方法万能且通用,有能力的还可以自行修改脚本. 工具 软件下载 anywebscript.com 方法/步骤 1.安装软件如图所示 2.设置脚本: (1)进入网站:[ ...

  4. Js 事件表格

  5. Alpha冲刺 —— 5.1

    这个作业属于哪个课程 软件工程 这个作业要求在哪里 团队作业第五次--Alpha冲刺 这个作业的目标 Alpha冲刺 作业正文 正文 github链接 项目地址 其他参考文献 无 一.会议内容 1.展 ...

  6. java方法句柄-----2.方法句柄的获取、变换、特殊方法句柄

    目录 1.获取方法句柄 1.1查找构造方法.一般方法和静态方法的方法句柄 1.2 查找类中的特殊方法(类中的私有方法) 1.3 查找类中静态域和一般域 1.4 通过反射API得到的Constructo ...

  7. Java实现蓝桥杯历届试题分考场

    历届试题 分考场 时间限制:1.0s 内存限制:256.0MB 提交此题 问题描述 n个人参加某项特殊考试. 为了公平,要求任何两个认识的人不能分在同一个考场. 求是少需要分几个考场才能满足条件. 输 ...

  8. Java实现 LeetCode 342 4的幂

    342. 4的幂 给定一个整数 (32 位有符号整数),请编写一个函数来判断它是否是 4 的幂次方. 示例 1: 输入: 16 输出: true 示例 2: 输入: 5 输出: false 进阶: 你 ...

  9. Java实现 LeetCode 191 位1的个数

    191. 位1的个数 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 '1' 的个数(也被称为汉明重量). 示例 1: 输入:000000000000000000000000000 ...

  10. Java实现 LeetCode 85 最大矩形

    85. 最大矩形 给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积. 示例: 输入: [ ["1","0","1 ...