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


相关文章
|
3月前
|
Rust 安全 编译器
Rust中避免常见错误:悬挂引用与生命周期不匹配
本文深入探讨了Rust编程语言中常见的两个内存管理错误:悬挂引用和生命周期不匹配,并提供了避免这些错误的实用方法。我们将详细解释这两种错误的来源,并通过示例展示如何在Rust中通过正确的生命周期标注和借用规则来避免它们,从而确保代码的内存安全性。
|
5月前
|
存储 Rust 安全
Rust 中的引用与借用
Rust 中的引用与借用
|
6月前
|
Rust 安全 JavaScript
Rust教程初识
当然,它们都有编译然后执行可执行程序的用法。应当有编译后改名的方式,不然那也太不coooooooooooool了。
45 0
|
10月前
|
存储 消息中间件 Rust
Rust极简教程
Rust是一门赋予每个人构建可靠且高效软件能力的编程语言。可靠主要体现在安全性上。其高效不仅限于开发效率,它的执行效率也是令人称赞的,是一种少有的兼顾开发效率和执行效率的语言。Rust 语言由 Mozilla 开发,最早发布于 2014 年 9 月。Rust 的编译器是在 MIT License 和 Apache License 2.0 双重协议声明下的免费开源软件。
248 0
Rust极简教程
|
存储 Rust JavaScript
Rust:为什么不能在同一个结构体中存储一个值和对该值的引用?(修改版)
基本把下面问题这个搞明白,就能彻底明白 Rust 语言的生命周期是怎么回事了。简而言之,生命周期不会改变你的代码,是你的生命控制生命周期,而不是生命周期在控制你的代码。换言之,生命周期是描述性的,而不是规定性的。
141 0
|
Rust 编译器
Rust 中级教程 第17课——引用的 lifetime(2)
Rust 中级教程 第17课——引用的 lifetime(2)
Rust 中级教程 第17课——引用的 lifetime(2)
|
Rust C++
【Rust 中级教程】 15 引用与借用(3)
【Rust 中级教程】 15 引用与借用(3)
【Rust 中级教程】 15 引用与借用(3)
|
20天前
|
Rust 安全 程序员
|
20天前
|
Rust 安全 程序员
Rust vs Go:解析两者的独特特性和适用场景
在讨论 Rust 与 Go 两种编程语言哪种更优秀时,我们将探讨它们在性能、简易性、安全性、功能、规模和并发处理等方面的比较。同时,我们看看它们有什么共同点和根本的差异。现在就来看看这个友好而公平的对比。
|
9月前
|
Rust Go C++
Rust vs Go:常用语法对比(十三)(1)
Rust vs Go:常用语法对比(十三)(1)
64 0