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

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

在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. step over、step into、step into my code、step out、run to cursor

    红 step over 跳过子函数 黄 step into 进入子函数 蓝 step into my code 不执行源码的子函数执行自己的 黑 step out 跳出当前函数 绿 run to cu ...

  2. web自动化之iframe操作

    from selenium import webdriver from selenium.webdriver.support.wait import WebDriverWait from seleni ...

  3. Magicodes.SwaggerUI 已支持.NET Core 3.1

    Magicodes.SwaggerUI 通过配置文件简单配置即可快速完成SwaggerUI的配置,包括: SwaggerUI的文档信息 API分组 API隐藏 API JSON生成(枚举.API架构I ...

  4. [Objective-C] 007_Foundation框架之NSString与NSMutableString

    在Cocoa Foundation中的NSString和NSMutableString类,为我们提供了Unicode字符串的支持,NSString和NSMutableString类最大的区别是:NSS ...

  5. MVC设计模式-查询与删除

    MVC是Model-View-Controller的简称,即模型-视图-控制器.MVC是一种设计模式,它把应用程序分成三个核心模块: 模型:模型是应用程序的主体部分,模型表示业务数据和业务逻辑. 一个 ...

  6. Python所有异常错误的父类--BaseException

    BaseException # 所有异常的基类 +-- SystemExit # 解释器请求退出 +-- KeyboardInterrupt # 用户中断执行(通常是输入^C) +-- Generat ...

  7. vc程序设计--图形输出3

    // 实验2.cpp : 定义应用程序的入口点. // #include "framework.h" #include "实验2.h" #define MAX_ ...

  8. [leetcode] 动态规划(Ⅰ)

    这次按通过率从高到低刷题. 本文完成的题目:{338, 1025, 303, 121, 53, 392, 70, 746, 198} ,带有「面试」Tag 的题目:Interview - {1617, ...

  9. Java实现 蓝桥杯VIP 算法训练 步与血(递推 || DFS)

    试题 算法训练 步与血 问题描述 有n*n的方格,其中有m个障碍,第i个障碍会消耗你p[i]点血.初始你有C点血,你需要从(1,1)到(n,n),并保证血量大于0,求最小步数. 输入格式 第一行3个整 ...

  10. Java实现 LeetCode 457 环形数组循环

    457. 环形数组循环 给定一个含有正整数和负整数的环形数组 nums. 如果某个索引中的数 k 为正数,则向前移动 k 个索引.相反,如果是负数 (-k),则向后移动 k 个索引.因为数组是环形的, ...