Rust 中级教程 第16课——引用的 lifetime(1)

简介: Rust 中级教程 第16课——引用的 lifetime(1)
+关注继续查看

开篇


01


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


题外话


02


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


0a2653c851af460fa595bd959398a8f1.png

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


lifetime (生命期/有效期)


03


我们先看一个小例子:

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

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


2d65d23f6d4748949b924e4057485923.png


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

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


6de278e6d6694ce5bb08e7e842b7e74b.png


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


8ec4f2997fb246878c34ecd6d122b7c6.png


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

 

生命期的使用


04


标注生命期


只有引用类型才需要标注 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。因此:生命期仅用于编译器的检查。


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


05


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

/// 拼接两个字符串
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 中禁止的。因此我们可以得出一个结论,当从一个函数/方法返回一个引用时,返回类型的生命期参数需要与其中一个参数的生命期参数相匹。当然也存在例外,继续往下看。


静态生命期


06


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

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


小结


07


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


相关文章
|
26天前
|
Rust 安全 JavaScript
Rust教程初识
当然,它们都有编译然后执行可执行程序的用法。应当有编译后改名的方式,不然那也太不coooooooooooool了。
16 0
|
5月前
|
存储 消息中间件 Rust
Rust极简教程
Rust是一门赋予每个人构建可靠且高效软件能力的编程语言。可靠主要体现在安全性上。其高效不仅限于开发效率,它的执行效率也是令人称赞的,是一种少有的兼顾开发效率和执行效率的语言。Rust 语言由 Mozilla 开发,最早发布于 2014 年 9 月。Rust 的编译器是在 MIT License 和 Apache License 2.0 双重协议声明下的免费开源软件。
186 0
Rust极简教程
|
11月前
|
Rust 编译器
Rust 中级教程 第17课——引用的 lifetime(2)
Rust 中级教程 第17课——引用的 lifetime(2)
Rust 中级教程 第17课——引用的 lifetime(2)
|
11月前
|
Rust C++
【Rust 中级教程】 15 引用与借用(3)
【Rust 中级教程】 15 引用与借用(3)
【Rust 中级教程】 15 引用与借用(3)
|
11月前
|
Rust
【Rust 中级教程】 14 引用与借用(2)
【Rust 中级教程】 14 引用与借用(2)
|
11月前
|
Rust 编译器
【Rust 中级教程】 13 引用与借用(1)
【Rust 中级教程】 13 引用与借用(1)
【Rust 中级教程】 13 引用与借用(1)
|
11月前
|
Rust 安全 编译器
【Rust 中级教程】 12 共享所有权
【Rust 中级教程】 12 共享所有权
【Rust 中级教程】 12 共享所有权
|
11月前
|
Rust
【Rust 中级教程】 11 所有权与trait(4)
【Rust 中级教程】 11 所有权与trait(4)
【Rust 中级教程】 11 所有权与trait(4)
|
11月前
|
Rust
【Rust 中级教程】 10 所有权(3)
【Rust 中级教程】 10 所有权(3)
【Rust 中级教程】 10 所有权(3)
|
11月前
|
Rust 安全 编译器
【Rust 中级教程】 09 所有权(2)
【Rust 中级教程】 09 所有权(2)
【Rust 中级教程】 09 所有权(2)
相关产品
云迁移中心
推荐文章
更多