DEV Community

sdttttt
sdttttt

Posted on

Rust 为什么需要生命周期注解

fn main() {
    let b;
    {
        let a = 10;
        b = &a;
    }       
    println!("{}", b);  
}

此时,a的作用域比b要小。rust的生命周期检查器发现a的生命周期比b要大,但是拥有一个生命周期比它小的对象,编译器就会拒绝编译。简而言之,rust的编译器能帮助我们确定某个引用是否有效(无效的话在编译时就会报错),以此来避免悬垂引用等一系列问题。

然而,现实是,编译器有时候并不能确定某个引用是否有效。我们来看下面这个例子:

fn longest_str(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let str1 = String::from("123");
    let str_longer;
    {
        let str2 = String::from("12345");
        str_longer = longest_str(&str1, &str2);
    }

    println!("{}",str_longer);
}

这段代码是无法编译通过的,报错信息如下:

error[E0106]: missing lifetime specifier
 --> src/main.rs:1:37
  |
1 | fn longest_str(x: &str, y: &str) -> &str {
  |                                     ^ expected lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0106`.
error: Could not compile `lifetime_test`.

To learn more, run the command again with --verbose.

表明在函数longest_str里需要生命周期注解,这是为什么呢?我们来分析一下代码。在上面这段代码里,str_longer拥有的引用是否有效呢?答案是不确定。我们只从函数内部来看是无法确认哪个参数的生命周期长或者是短.

需要注意的是,生命周期注解并不改变任何引用的生命周期的长短。也就是说,单个生命周期其实是没什么意义的,生命周期最大的作用就是告诉 Rust 多个引用的泛型生命周期参数如何相互联系的。加入有一个有一个生命周期为'a的参数x,一个生命周期也为'a的参数y,那么就说明x与y的生命周期是一样长的。这么一来就可以解决刚才longest_str函数的问题了。我们给函数标上生命周期注解:

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

fn main() {
    let str1 = String::from("123");
    let str_longer;
    {
        let str2 = String::from("12345");
        str_longer = longest_str(&str1, &str2);
    }

    println!("{}",str_longer);
}

虽然仍然无法编译通过,但是错误信息变成了:

error[E0597]: `str2` does not live long enough
  --> src/main.rs:14:41
   |
14 |         str_longer = longest_str(&str1, &str2);
   |                                         ^^^^^ borrowed value does not live long enough
15 |     }
   |     - `str2` dropped here while still borrowed
16 |
17 |     println!("{}",str_longer);
   |                   ---------- borrow later used here

即错误信息变成了编译器检查出引用生命周期无效了,而不是longest_str这个函数的错误了,即str2和str1生命周期不一致。我们只需要修改main函数里参数的生命周期,使str1与str2生命周期保持一致即可。

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

fn main() {
    let str1 = String::from("12345");
    let str_longer;
    let str2 = String::from("123");
    str_longer = longest_str(&str1, &str2);

    println!("{}",str_longer);
}

那么问题来了,在上面这段代码里,函数longest_str里,x,y和函数返回值的生命周期都是一致的,那我们可不可以将它们标注成不同的呢?就像

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

这并没有任何意义, 生命周期注解说到底也只是起一个标识作用.并不会改变程序的任何逻辑.它只是用来给Rust生命周期检查器核对的.

最后,总结一下,当且仅当输出值的生命周期为所有输入值的生命周期交集的子集时,生命周期合法。这个应该很好理解,也很好的解释了我们举的例子中生命周期注解的用法。rust也正是通过这种方式解决了"共享"这一问题,从某种程度上保证了内存的安全。

总结

生命周期注解只是给编译器检查使用的。
注解本身无法改变任何生命周期。
但是标出后能尽量让程序和你预期的一样工作。

Top comments (0)