rust 函数-生命周期

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

 

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

在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 就有了足够的信息来允许内存安全的操作并阻止会产生悬垂指针亦或是违反内存安全的行为。

 

posted @ 2020-05-15 14:09  Please Call me 小强  阅读(1220)  评论(0编辑  收藏  举报