【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

相關文章
相關標籤/搜索