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

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

在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. PHP基础-自定义函数-变量范围-函数参数传递

    一.自定义函数    function 函数名([形式参数1,形式参数2,....形式参数n]){        //各种PHP代码....        //......        return ...

  2. 【NLP】常用优化方法

    目录 梯度下降法 动量法 AdaGrad算法 RMSProP算法 AdaDelta算法 Adam算法 1.梯度下降法 梯度下降法可以分为三种,批量梯度下降法(BGD).小批量梯度下降(MBGD).随机 ...

  3. [VuePress]个人博客 -- 批处理自动化编译提交 -- 排错记录

    建了一个VuePress的个人博客 想着写个批处理,自动编译并上传到GitHub. 结果遇到两个问题, 一个是,vuepress build docs编译后,这个命令执行完就exit了 研究了下bat ...

  4. 【QT】利用pyqt5实现简单界面

    Topic: 利用pyqt5编写简单界面Env:win10 + Pycharm2018 + Python 3.6.8Date: 2019/4/29 by hw_Chen2018            ...

  5. 小智的糖果(Candy) 51nod 提高组试题

    luogu AC通道! (官方数据) 题目描述 小智家里来了很多的朋友,总共有N个人,站成一排,分别编号为0到N-1,小智要给他们分糖果.但 是有的朋友有一些特殊的要求,有的人要求他左右的两个人(左边 ...

  6. BZOJ1003 物流运输 题解

    发现\(n,m\)很小,我们可以先把任意\(2\)天的最短路都给求出来,考虑\(DP\),设\(f[i][j]\)表示\(j+1\)~ \(i\)这几天内走的是最短路线的最优方案,显然最优情况下\(j ...

  7. 设计一个多功能的MyTime类 代码参考

    #include <iostream> #include <cstdio> using namespace std; class MyTime { private: int h ...

  8. jchdl - RTL实例 - Counter4

    https://mp.weixin.qq.com/s/xtvMj5f-Uvx3vesVnH0P_A   计数器.   参考链接 https://github.com/wjcdx/jchdl/blob/ ...

  9. Jmeter(七) - 从入门到精通 - 建立数据库测试计划实战<MySQL数据库>(详解教程)

    1.简介 在实际工作中,我们经常会听到数据库的性能和稳定性等等,这些有时候也需要测试工程师去评估和测试,上一篇文章宏哥主要介绍了jmeter连接和创建数据库测试计划的过程,宏哥在文中通过示例和代码非常 ...

  10. Java实现第八届蓝桥杯国赛 数字划分

    标题:数字划分 w星球的长老交给小明一个任务: 1,2,3-16 这16个数字分为两组. 要求: 这两组数字的和相同, 并且,两组数字的平方和也相同, 并且,两组数字的立方和也相同. 请你利用计算机的 ...