likes
comments
collection
share

【Rust 中级教程】16 引用的 lifetime(1)本篇文章将介绍 Rust 的 lifetime。lifetim

作者站长头像
站长
· 阅读数 34

0x01 开篇

本篇文章将介绍 Rust 的 lifetime。lifetime 这也是 Rust 中的重点。简单了解下 Rust 的生命期的概念以及简单使用。本篇文章的阅读时间大约 10 分钟

0x02 题外话

文章开头,我建议大家不要将 lifetime 翻译成 生命周期 。其实life circle 这个词组的意思才是 生命周期。来看下lifetime  的有道翻译。

【Rust 中级教程】16 引用的 lifetime(1)本篇文章将介绍 Rust 的 lifetime。lifetim

通常我们所说的生命周期应该是存在一个循环过程的。另外,在 Rust 中,只有引用类型才需要标注 lifetime。lifetime 是用来保证引用类型在使用时是有效的,并且在使用结束后释放所占用的内存的一种机制。 所以,我们可以将其翻译为引用的生存期,引用的有效期,引用的使用期等等。

0x03 lifetime (生命期/有效期)

我们先看一个小例子:

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

这段代码是编译不通过的,来看下编译器给出的错误。

【Rust 中级教程】16 引用的 lifetime(1)本篇文章将介绍 Rust 的 lifetime。lifetim

字面意思就是 b 活的不够长,很通俗易懂哈哈。简单分析下:变量 a 是一个 &i32 引用类型,我们在内部代码块中初始化 a,但是当内部代码块执行结束后,变量 b 离开作用域被释放了,但是 a 没有被释放,这时 a 就会变成 悬垂指针,当然这在 Rust 中是绝对不允许的。

理论上来讲,其实所有的变量都存在生存期,变量的生命期一定是包含引用的生存期。先来看下面这张图片,红框所示的区域是变量a的生命期。蓝框所示的是b的作用域(生命期)。很显然b的作用域没有包含a,变量的生命期没有包含引用的生命期,这种做法是禁止的。

【Rust 中级教程】16 引用的 lifetime(1)本篇文章将介绍 Rust 的 lifetime。lifetim

我们转换下代码,如下图:

【Rust 中级教程】16 引用的 lifetime(1)本篇文章将介绍 Rust 的 lifetime。lifetim

很显然b的作用域包含a,变量的生命期包含了引用的生命期。这段代码是可以正常编译的。

0x04 生命期的使用

标注生命期

只有引用类型才需要标注 lifetime。因此,以&i32 为例,标注生命期后变为 &'a i32 ,在 & 后添加 'a,通常叫做生命期 a。a 可以被更换,其命名规则参考变量的命名规则。

  • &'a i32  标注生命期 a 的共享引用
  • &'a mut i32  标注生命期 a 的可变引用
函数/方法签名中的生命期标注

编译器通常会推断生命期,当然我们也可以标注生命期。通常我们写函数/方法时是下面的写法。

fn test(name: &str) -> &str {
    println!("{}", name);
    return name;
}

其实,这里是存在生命期标注的,如果编译器可以自动推断生命期时,则无需标注。上面的函数添加生命期标注后如下所示:

fn test_life<'_a>(name: &'_a str) -> &'_a str {
    println!("{}", name);
    return name;
}

在函数名后面,添加<'a>,如同泛型,在标注前先声明。然后再对每个参数或者返回值标注。

为什么存在生命期?

生命期仅用于编译器的检查。并不会更改原有生命期的长短。举个简单的例子,下面的代码是传入两个字符串,返回最长的那个字符串。

fn main() {
    let x = String::from("xxx");
    let y = "yyyy";

    let z = longest(x, y);

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

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

如果我们直接编译,会提示错误。

error[E0106]: missing lifetime specifier
  --> src\main.rs:31:33
   |
31 | fn longest(x: &str, y: &str) -> &str {
   |               ----     ----     ^ expected named 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`
help: consider introducing a named lifetime parameter
   |
31 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
   |           ++++     ++          ++          ++

For more information about this error, try `rustc --explain E0106`.
error: could not compile `lifetime` due to previous error

错误提示告诉我们,缺少生命期标识符。在当编译时期,rust 并不知道我们返回的是 x 还是 y,因此不能确定返回的字符串的生命期。这个函数主体中, if 块返回的是 x 的引用,而 else 块返回的是 y 的引用。所以我们需要标注生命期,来告诉编译器。

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

标注传入参数的生命期都是 a,返回值的生命期也是 a,所以无论返回 x 还是 y,都是生命期 a 的 &str。因此:生命期仅用于编译器的检查。

0x05 引用作为函数/方法返回值(接上节)

我们再看下面一个例子,编译会发现报错:

/// 拼接两个字符串
fn concat_str<'a>(x: &'a str, y: &'a str) -> &'a str {
    let s = format!("{}{}",x, y);
    return s.as_str();
}

下面是错误,不能返回一个局部变量:

error[E0515]: cannot return reference to local variable `s`
  --> src\main.rs:36:12
   |
36 |     return s.as_str();
   |            ^^^^^^^^^^ returns a reference to data owned by the current function

For more information about this error, try `rustc --explain E0515`.
error: could not compile `lifetime` due to previous error

我们思考,假设可以通过编译,会发生什么?当函数结束后,s 被释放,返回的引用会变成 悬垂引用,这种做法是在 rust 中禁止的。因此我们可以得出一个结论,当从一个函数/方法返回一个引用时,返回类型的生命期参数需要与其中一个参数的生命期参数相匹配。当然也存在例外,继续往下看。

0x06 静态生命期

在 Rust 中,存在一种静态生命期 'static。它表示数据在程序的整个运行期间都有效,它常用于储存全局静态数据和字符串常量。像一些字符串字面量,字节字符串字面量等等这些类似的生命期默认是 'static。在函数里,可以直接返回 'static 的生命期。

fn get_any_str() -> &'static str {
    return "static";
}

0x07 小结

本章仅仅是简单了解下 lifetime,lifetime 是 Rust 中用来保证引用在使用时是有效的。生命期并不会改变在方法和函数中返回引用时,如果返回的引用不指向其中一个参数,那么它必须指向在这个函数中创建的一个值,然而这将会产生悬垂引用。本篇文章写的有些仓促,望大家见谅!

转载自:https://juejin.cn/post/7223966983917174843
评论
请登录