Rust 中级教程 第17课——引用的 lifetime(2)

简介: Rust 中级教程 第17课——引用的 lifetime(2)

0x00 开篇


本篇文章将继续介绍 Rust 的 lifetime 在结构体中的使用以及 lifetime 的省略规则。本篇文章的阅读时间大约 5 分钟。


0x01 结构体中的引用


先来看一个示例:

fn main() {
    let name = "zhangsan";
    let age = 18;
    let s = Student { name: name, age: &age };
    println!("{:?}", s);
}
#[derive(Debug)]
struct Student {
    name: &str,
    age: &i32,
}

上面的代码乍一看是没有问题的,但是当编译时会提示错误,来看下错误信息。


0a2653c851af460fa595bd959398a8f1.png


错误提示告诉我们,"缺少生命期标识符"。Rust 有个原则,当引用类型出现在另一个类型的定义中时,必须为引用标注生命期。上面的代码有两中解决办法:所有的引用都标注静态生命期,或者指定一个自定义的生命期。

  • 所有的引用都标注静态生命期(不推荐)
fn main() {
    let name = "zhangsan";
    const age: i32 = 30;
    let s = Student { name: name, age: &age };
    println!("{:?}", s);
}
#[derive(Debug)]
struct Student {
    name: &'static str,
    age: &'static i32,
}

这样改虽然能解决问题,但是我们并不推荐。这样会导致所有的值的生命期都跟整个程序一样了。

  • 指定一个自定义的生命期

我们可以指定一个生命期a,让每个结构体都拥有一个生命期 a。跟类似声明泛型一样,在结构体的后面添加 <'a>,为每个字段标注生命期。

fn main() {
    let name = "zhangsan";
    let age: i32 = 18;
    let s = Student { name: name, age: &age };
    println!("{:?}", s);
}
#[derive(Debug)]
struct Student<'a> {
    name: &'a str,
    age: &'a i32,
}

现在的每个 Student 类型都拥有一个生命期 a,保存在 nameage 中任何引用的值都包含生命期 a,生命期a 也必须比保存 Student 的值长。


0x02 哪些值具有 'static 生命期


继续观察上面的代码,当我使用 &'static i32' 时,传入了一个 const 值的引用,而name却使用的let,这里的const可以改成 let 吗?答案是不行。

要搞清楚这个问题,首先要明白在 Rust 中哪些值的生命期是 'static',上一篇文章我只是简单说了下字符串字面量是静态生命期,那还有哪些值具有静态生命期呢?下面先看一张图。


2d65d23f6d4748949b924e4057485923.png


这是一张内存简易图,拥有静态生命期的区域就是紫色区域了。

  • BSS Segment:存放全局变量和静态变量的一块内存区域,可读写。
  • Data Segment:存存放常量、字符串字面量等数据的区域,只读。
  • Text Segment:存放代码片段的区域,只读。

这三个区域的生命期与程序的生命期一致,所以在这些区域保存的值拥有静态生命期,他们都有个特点就是编译前这些值就是确定的。分配在堆和栈上的值的生命期是动态的。另外,如果在堆上使用了Box::leak,那么也具有静态生命期了,使用 Box::leak 就可以将一个运行期的值转为 'static (暂时了解即可)。


0x03 生命期省略规则


从我们开始学习到现在可以发现很少需要我们主动标明生命期。虽然没有标注,但是其实生命期是存在的。Rust 会在生命期参数合理时省略他们。三条省略的规则如下:

  • 如果一个函数的返回值不返回任何引用,那么永远都不需要标注参数的生命期。
fn main() {      let val1 = 5;      let val2 = 6;      let x = fun1(&val1, &val2);      println!("fun1 = {}", x);  }  
  // 无需标注生命期  fn fun1(a: &i32, b: &i32) -> i32 {      return a + b + 5;  }  
  // 运行结果  // fun1 = 16

如果一个函数的参数只出现了一个生命期且返回值是引用,那么Rust则推断返回值的生命期与函数参数的生命期相同,也不需要标注生命期。

fn main() {   let array = [6, 3];      let x = fun2(&array);      println!("fun2 = {:?}", x);  }  
  fn fun2(a: &[i32; 2]) -> (&i32, &i32) {      return (&a[0], &a[1]);  }  
  // 运行结果  // fun2 = (6, 3)  

如果函数是某个类型的方法,方法本身会接收引用形式的 self 参数,那么 Rust 将会把返回值的生命期推断为 self 的生命期。

fn main() {   let a = String::from("aaa");      let b = String::from("bbb");      let c = String::from("ccc");      let example = Example { data: vec![a, b, c] };      let x = example.get_element("b");      println!("fun3 = {:?}", x);  }  
  struct Example {      data: Vec<String>,  }  
  impl Example {      /// 通过前缀查找字符串      fn get_element(&self, prefix: &str) -> Option<&String> {          for i in 0..self.data.len() {              if self.data[i].starts_with(prefix) {                  return Some(&self.data[i]);              }          }          None      }  }

在这个例子中,完整的函数签名应该是 fn get_element<'a,'b>(&'a self, prefix: &'b str) -> Option<&'a String> , Rust 会假定你无论借用什么,都是用 self 中借用的。

PS:如果一个函数存在返回值但是没有函数入参,那么返回值的生命期将会推断为 'static。不满足上面两条规则的将必须添加生命期。比如我们上一篇文章讲的示例 longest(x: &str, y: &str) -> &str


0x04 小结


生命期的标注仅仅是为了告知编译器,而在我们平时的大部分场景下基本都不需要标注生命期,一定要牢记省略生命期的三条规则。另外,当引用类型出现在另一个类型的定义中时,必须为引用标注生命期。


相关文章
|
4月前
|
Rust 编译器 程序员
Rust与C++的区别及使用问题之Rust避免多线程中的lifetime的问题如何解决
Rust与C++的区别及使用问题之Rust避免多线程中的lifetime的问题如何解决
|
4月前
|
Rust
rust 引用了Trait的实现,为什么还需要引入Trait 才能调用实现的方法
rust 引用了Trait的实现,为什么还需要引入Trait 才能调用实现的方法
|
5月前
|
Rust 安全 开发者
Rust引用、借用和所有权详解
Rust引用、借用和所有权详解
|
6月前
|
Rust 编译器
【Rust】——函数(所有权)以及借用或引用
【Rust】——函数(所有权)以及借用或引用
|
6月前
|
Rust 安全 编译器
Rust中避免常见错误:悬挂引用与生命周期不匹配
本文深入探讨了Rust编程语言中常见的两个内存管理错误:悬挂引用和生命周期不匹配,并提供了避免这些错误的实用方法。我们将详细解释这两种错误的来源,并通过示例展示如何在Rust中通过正确的生命周期标注和借用规则来避免它们,从而确保代码的内存安全性。
|
6月前
|
存储 Rust 安全
Rust 中的引用与借用
Rust 中的引用与借用
|
12月前
|
Rust 安全 JavaScript
Rust教程初识
当然,它们都有编译然后执行可执行程序的用法。应当有编译后改名的方式,不然那也太不coooooooooooool了。
76 0
|
存储 消息中间件 Rust
Rust极简教程
Rust是一门赋予每个人构建可靠且高效软件能力的编程语言。可靠主要体现在安全性上。其高效不仅限于开发效率,它的执行效率也是令人称赞的,是一种少有的兼顾开发效率和执行效率的语言。Rust 语言由 Mozilla 开发,最早发布于 2014 年 9 月。Rust 的编译器是在 MIT License 和 Apache License 2.0 双重协议声明下的免费开源软件。
318 0
Rust极简教程
|
存储 Rust JavaScript
Rust:为什么不能在同一个结构体中存储一个值和对该值的引用?(修改版)
基本把下面问题这个搞明白,就能彻底明白 Rust 语言的生命周期是怎么回事了。简而言之,生命周期不会改变你的代码,是你的生命控制生命周期,而不是生命周期在控制你的代码。换言之,生命周期是描述性的,而不是规定性的。
176 0
|
Rust 编译器
Rust 中级教程 第16课——引用的 lifetime(1)
Rust 中级教程 第16课——引用的 lifetime(1)
Rust 中级教程 第16课——引用的 lifetime(1)