【Rust学习笔记】Rust生命周期参数的详细阐述

Rust生命周期javascript

程序中每一个变量都有一个固定的做用域,当超出变量的做用域之后,变量就会被销毁。变量在做用域中从初始化到销毁的整个过程称之为生命周期。
rust的每一个函数都会有一个做用域,也能够在函数中使用一对花括号再内嵌一个做用域。好比以下代码中就在main函数的函数做用域中又内嵌了一个做用域:
  
    
  
  
  
   
   
            
   
   

  
    
  
  
  
   
   
            
   
   
fn main() { let a; // --------------+-- a start { // | let b = 5; // -+-- b start | } // -+-- b over |} // --------------+-- a over
上面代码存在两个做用域,一个是 main 函数自己的做用域,另一个是在 main 函数中使用一对 {} 定义了一个内部做用域。第2行代码声明了变量 a ,它的做用域是整个 main 函数,也能够说它的生命周期是从第2行代码到第6行代码。在第4行代码中声明了变量 b ,它的做用域是第4行到第6行。咱们能够发现变量的生命周期是有长短的。

生命周期与借用

rust中的借用是指对一块内存空间的引用。rust有一条借用规则是借用方的生命周期不能比出借方的生命周期还要长。
例如:
  
    
  
  
  
   
   
            
   
   

  
    
  
  
  
   
   
            
   
   
fn main() { let a; // -------------+-- a start { // | let b = 5; // -+-- b start | a = &b; // | | } // -+-- b over | println!("a: {}", a); // |}                       // -------------+-- a over
上面第5行代码把 变量b 借给了 变量a ,因此a是借用方,b是出借方。能够发现 变量a (借用方)的生命周期比 变量b (出借方)的生命周期长,因而这样作违背了rust的借用规则(借用方的生命周期不能比出借方的生命周期还要长)。由于当b在生命周期结束时,a仍是保持了对b的借用,就会致使a所指向的那块内存空间已经被释放了,那么变量a就会是一个悬垂引用。
运行上面代码会报以下错误:
  
    
  
  
  
   
   
            
   
   

  
    
  
  
  
   
   
            
   
   
error[E0597]: `b` does not live long enough --> src/main.rs:5:13 |5 | a = &b; | ^^ borrowed value does not live long enough6 | }; | - `b` dropped here while still borrowed7 | println!("a:{}", a);  |                      - borrow later used here
意思就是说变量b的生命周期不够长。变量b已经被销毁了仍然对它进行了借用。
一个正确的例子:
  
    
  
  
  
   
   
            
   
   

  
    
  
  
  
   
   
            
   
   
fn main() { let a = 1; // -------------+-- a start let b = &a; // -------------+-- b start println!("a: {}", a); // |}                       // -------------+-- b, a over
观察上面代码发现变量b(借用方)的生命周期要比变量a(出借方)的生命周期要短,因此借用检查器会经过。

函数中的生命周期参数

对于一个参数和返回值都包含引用的函数而言,该函数的参数是出借方,函数返回值所绑定到的那个变量就是借用方。因此这种函数也须要知足借用规则(借用方的生命周期不能比出借方的生命周期还要长)。那么就须要对函数返回值的生命周期进行标注,告知编译器函数返回值的生命周期信息。
咱们下面定义一个函数,该函数接收两个 i32 的引用类型,返回大的那个数的引用。
示例:
  
    
  
  
  
   
   
            
   
   

  
    
  
  
  
   
   
            
   
   
fn max_num(x: &i32, y: &i32) -> &i32 { if x > y { &x } else { &y }}
fn main() { let x = 1; // -------------+-- x start let max; // -------------+-- max start { // | let y = 8; // -------------+-- y start max = max_num(&x, &y); // | } // -------------+-- y over println!("max: {}", max); // |}                           // -------------+-- max, x over
因为缺乏生命周期参数,编译器不知道 max_num 函数返回的引用生命周期是什么,因此运行报错:
error[E0106]: missing lifetime specifier --> src/main.rs:1:33 |1 | fn max_num(x: &i32, y: &i32) -> &i32 { | ---- ---- ^ 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 `

函数的生命周期参数声明在函数名后的尖括号<>里,而后每一个参数名跟在一个单引号'后面,多个参数用逗号隔开。若是在参数和返回值的地方须要使用生命周期进行标注时,只须要在&符号后面加上一个单引号'和以前声明的参数名便可。生命周期参数名能够是任意合法的名称。例如:java

fn max_num<'a>(x: &'a i32, y: &'a i32) -> &'a i32 { if x > y { &x } else { &y }}fn main() { let x = 1; // -------------+-- x start let max; // -------------+-- max start { // | let y = 8; // -------------+-- y start max = max_num(&x, &y); // | } // -------------+-- y over println!("max: {}", max); // |}                           // -------------+-- max, x over
上面代码对函数的参数和返回值的生命周期进行了标注,用于告诉编译器函数参数和函数返回值的生命周期同样长。在第13行代码对进行调用时,编译器会把变量x的生命周期和变量y的生命周期与函数的生命周期参数创建关联。这里值得注意的是,变量x和变量y的生命周期长短实际上是不同的,那么关联到max_num函数的生命周期参数的长度是多少呢?实际上编译器会取变量x的生命周期和变量y的生命周期的部分,也就是取最短的那个变量的生命周期与创建关联。这里最短的生命周期是变量,因此关联的生命周期就是变量y的生命周期。
max_nummax_nummax_num'a'a重叠'ay'a
运行上面代码,会有报错信息:
  
    
  
  
  
   
   
            
   
   

  
    
  
  
  
   
   
            
   
   
error[E0597]: `y` does not live long enough --> src/main.rs:13:27 |13 | max = max_num(&x, &y); | ^^ borrowed value does not live long enough14 | } | - `y` dropped here while still borrowed15 | println!("max: {}", max);   |                         --- borrow later used here
报错信息说变量y的生命周期不够长,当y的生命周期结束后,仍然被借用。
咱们仔细观察发现 max_num 函数返回值所绑定到的那个变量 max (借用方)的生命周期是从第10行代码到第16行代码,而 max_num 函数的返回值(出借方)的生命周期是 'a 'a 的生命周期又是变量x的生命周期和变量y的生命周期中最短的那个,也就是变量y的生命周期。变量y的生命周期是代码的第12行到第14行。因此这里不知足借用规则(借用方的生命周期不能比出借方的生命周期还要长)。也就是为何编译器会说变量y的生命周期不够长的缘由了。函数的生命周期参数并不会改变生命周期的长短,只是用于编译来判断是否知足借用规则。
将代码作以下调整,使其变量max的生命周期小于变量y的生命周期,编译器就能够正常经过:
fn max_num<'a>(x: &'a i32, y: &'a i32) -> &'a i32 { if x > y { &x } else { &y }}fn main() { let x = 1; // -------------+-- x start let y = 8; // -------------+-- y start let max = max_num(&x, &y); // -------------+-- max start println!("max: {}", max); // |}                             // -------------+-- max, y, x over
函数存在多个生命周期参数时,须要标注各个参数之间的关系。例如:
  
    
  
  
  
   
   
            
   
   

  
    
  
  
  
   
   
            
   
   
fn max_num<'a, 'b: 'a>(x: &'a i32, y: &'b i32) -> &'a i32 { if x > y { &x } else { &y }}fn main() { let x = 1; // -------------+-- x start let y = 8; // -------------+-- y start let max = max_num(&x, &y); // -------------+-- max start println!("max: {}", max); // |}                             // -------------+-- max, y, x over
上面代码使用 'b: 'a 来标注 'a 'b 之间的生命周期关系,它表示 'a 的生命周期不能超过 'b ,即函数返回值的生命周期 'a (借用方)不能超过 'b``(出借方), 'a 也不会超过 'a`(出借方)。

结构体中的生命周期参数

一个包含 引用成员 的结构体,必须保证结构体自己的生命周期不能超过任何一个 引用成员 的生命周期。不然就会出现成员已经被销毁以后,结构体还保持对那个成员的引用就会产生悬垂引用。因此这依旧是rust的借用规则即借用方(结构体自己)的生命周期不能比出借方(结构体中的引用成员)的生命周期还要长。所以就须要在声明结构体的同时也声明生命周期参数,同时对结构体的引用成员进行生命周期参数标注。
结构体生命周期参数声明在结构体名称后的尖括号 <> 里,每一个参数名跟在一个单引号 ' 后面,多个参数用逗号隔开。在进行标注时,只须要在引用成员的 & 符号后面加上一个单引号 ' 和以前声明的参数名便可。生命周期参数名能够是任意合法的名称。例如:
  
    
  
  
  
   
   
            
   
   

  
    
  
  
  
   
   
            
   
   
struct Foo<'a> { v: &'a i32}
上面代码能够把结构体 Foo 的生命周期与成员 v 的生命周期创建一个关联用于编译器进行借用规则判断。
函数生命周期参数要注意一点的是,若是函数的参数与函数的返回值不创建生命周期关联的话,生命周期参数就毫无用处。
下面是一个违反借用规则的例子:
  
    
  
  
  
   
   
            
   
   

  
    
  
  
  
   
   
            
   
   
#[derive(Debug)]struct Foo<'a> { v: &'a i32}
fn main() { let foo; // -------------+-- foo start { // | let v = 123; // -------------+-- v start foo = Foo { // | v: &v // | } // | } // -------------+-- v over println!("foo: {:?}", foo); // |}                             // -------------+-- foo over

上面代码的第14行到15行foo的生命周期依然没有结束,可是它所引用的变量v已经被销毁了,所以出现了悬垂引用。编译器会给出报错提示:变量v的的生命周期不够长。nginx

静态生命周期参数

有一个特殊的生命周期参数叫 static ,它的生命周期是整个应用程序。跟其余生命周期参数不一样的是,它是表示一个具体的生命周期长度,而不是泛指。 static 生命周期的变量存储在静态段中。
全部的字符串字面值都是 'static 生命周期,例如:
  
    
  
  
  
   
   
            
   
   

  
    
  
  
  
   
   
            
   
   
let s: &'static str = "codercat is a static lifetime.";
上面代码中的生命周期参数能够省略,就变成以下形式:
  
    
  
  
  
   
   
            
   
   

  
    
  
  
  
   
   
            
   
   
let s: &str = "codercat is a static lifetime.";
还有 static 变量的生命周期也是 'static
例如:
  
    
  
  
  
   
   
            
   
   

  
    
  
  
  
   
   
            
   
   
static V: i32 = 123;
下面举一个特殊的例子:
  
    
  
  
  
   
   
            
   
   

  
    
  
  
  
   
   
            
   
   
fn max_num<'a>(x: &'a i32, y: &'a i32) -> &'a i32 { if x > y { &x } else { &y }}fn main() { let x = 1; // -------------+-- x start let max; // -------------+-- max start { // | static Y: i32 = 8; // -------------+-- Y start max = max_num(&x, &Y); // | } // | println!("max: {}", max); // |} // -------------+-- max, Y, x over
仍是以前的 max_num 函数。在代码的第12行定义了一个静态变量,它的生命周期是 'static max_num 函数的生命周期参数 'a 会取变量 x 的生命周期和变量 Y 的生命周期重叠的部分。因此传入 max_num 函数并不会报错。

总结

以上内容是我我的在学习rust生命周期参数相关内容时的总结,若有错误欢迎指正。文中的借用和引用其实是一个东西。

本文分享自微信公众号 - Rust语言中文社区(rust-china)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。swift

相关文章
相关标签/搜索