Rust入坑指南:朝生暮死

今天想和你們一塊兒把咱們以前挖的坑再刨深一些。在Java中,一個對象能存活多久全靠JVM來決定,程序員並不須要去關心對象的生命週期,可是在Rust中就大不相同,一個對象從生到死咱們都須要掌握的很清楚。 git

Rust入坑指南:核心概念一文中咱們介紹了Rust的幾個核心概念:全部權(Ownership)、全部權轉移和全部權借用。今天就來介紹Rust中的另一個核心概念:生命週期。程序員

爲何生命週期要單獨介紹呢?由於我在這以前一直沒搞清楚Rust中的生命週期參數到底是怎麼一回事。github

如今我終於弄明白了,因而火燒眉毛要和你們分享,固然若是我有什麼說的不對的地方請幫忙指正。函數

在Rust中,值的生命週期與做用域有關,這裏你能夠結合全部權一塊兒理解。在一個函數內,Rust中值的全部權的範圍即爲其生命週期。Rust經過借用檢查器對值的生命週期進行檢查,其目的是爲了不出現懸垂指針。這點很容易理解,咱們經過一段簡單的代碼來看一下。學習

fn main() {
    let a;  // 'a ---------------+
    {                   //       |
        let b = 1; // 'b ----+   |
        a = &b;           // |   |
    }// ---------------------+   |
    println!("a: {}", a); //     |
} // ----------------------------+

在上面這段代碼中,我已經標註了a和b的生命週期。在代碼的第5行,b將全部權出借給了a,而在第7行咱們想使用a時,b的生命週期已經結束,也就是說,從第7行開始,a成爲了一個懸垂指針。所以這段代碼會報一個編譯錯誤。指針

生命週期編譯錯誤

而當全部權在函數之間傳遞時,Rust的借用檢查器就沒有辦法來肯定值的生命週期了。這個時候咱們就須要藉助生命週期參數來幫助Rust的借用檢查器來進行生命週期的檢查。生命週期參數分爲顯式的和隱式的兩種。code

顯式生命週期參數

顯式生命週期的標註方式一般是'a這樣的。它應該寫在&以後,mut以前(若是有)。對象

函數簽名中的生命週期參數

在正式開始學習以前,咱們還要先明確一些概念。下面是一個代有生命週期參數的函數簽名。blog

fn foo <'a>(s: &'a str, t: &'a str) ->&'a str;

其中第一個'a,是生命週期參數的聲明。參數的生命週期叫作輸入聲明週期,返回值的生命週期叫作輸出生命週期。須要記住的一點是:輸出的生命週期長度不能長於輸入的生命週期生命週期

另外還要注意:禁止在沒有任何輸入參數的狀況下返回引用。由於這樣明顯會形成懸垂指針。試想當你沒有任何輸入參數時返回了引用,那麼引用自己的值在函數返回時必然會被析構,返回的引用也就成了懸垂指針。

一樣的道理咱們能夠得出另外一個結論:從函數中返回一個引用,其生命週期參數必須與函數的參數相匹配,不然,標註生命週期參數也毫無心義

說了這麼多「不容許」以後,咱們來看一個正常使用生命週期參數的例子吧。

fn the_longest<'a> (s1: &'a str, s2: &'a str) -> &'a str {
    if s1.len() > s2.len() {
        s1
    } else {
        s2
    }
}
fn main() {
    let s1 = String::from("Rust");
    let s1_r = &s1;
    {
        let s2 = String::from("C");
        let res = the_longest(s1_r, &s2);
        println!("{} is the longest", res);
    }
}

咱們來看看這段代碼的各個值的生命週期是否符合咱們前面說的那一點原則。在調用th_longest函數時,兩個參數的生命週期已經肯定,s1的生命週期貫穿了main函數,s2的生命週期在內部的代碼塊中。函數返回時,將返回值綁定給了res,也就是說返回的生命週期爲res的生命週期,因爲後定義先析構的原則,res的生命週期是短於s2的生命週期的,固然也短於s1的生命週期。所以這個例子符合了咱們說的輸出的生命週期長度不能長於輸入的生命週期的原則。

對於像示例當中有多個參數的函數,咱們也能夠爲其標註不一樣的生命週期參數,可是編譯器沒法肯定兩個生命週期參數的大小,所以須要咱們顯式的指定。

fn the_longest<'a, 'b: 'a> (s1: &'a str, s2: &'b str) -> &'a str {
    if s1.len() > s2.len() {
        s1
    } else {
        s2
    }
}

這裏'b: 'a的意思是'b的存活週期長於'a。這點有些使人疑惑,'a明明是長於'b的,爲何會這樣標註呢?還記得咱們說過生命週期參數的意義嗎?它是用來幫助Rust借用檢查器來檢查非法借用的,輸出生命週期必須短於輸入生命週期。所以這裏的'a其實是返回值的生命週期,而不是第一個輸入參數的生命週期。

函數中的生命週期參數的使用咱們暫時先介紹到這裏。生命週期在其餘使用場景中的使用方法也比較相似,不過仍是有一些值得注意的地方的。

結構體中的生命週期參數

若是一個結構體包含引用類型的成員,那麼結構體應該聲明生命週期參數<'a>。這是爲了保證結構體實例的生命週期應該短於或等於任意一個成員的生命週期

struct ImportantExcept<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("call me Ishmael. Some year ago...");
    let first_sentence = novel.split('.')
        .next()
        .expect("Could not find a '.'");
    let i = ImportantExcept { part: first_sentence};
    assert_eq!(i.part, "call me Ishmael");
}

在這段代碼中first_sentence先於結構體實例i被定義,所以i的生命週期是短於first_sentence的,若是反過來,i的生命週期長於first_sentence即長於part,那麼在part被析構之後,i.part就會成爲懸垂指針。

方法中的生命週期參數

如今咱們爲剛纔的結構體增長一個實現方法

impl<'a> ImportantExcept<'a> {
    fn get_first_sentence(s: &'a str) -> &'a str {
        let first_sentence = s.split('.')
            .next()
            .expect("Could not find a '.'");
        first_sentence
    }
}

由於ImportantExcept包含引用成員,所以須要標註生命週期參數。在impl後面聲明生命週期參數<'a>在結構體名稱後面使用。在get_first_sentence方法中使用的生命週期參數也是剛剛定義好的那個。這樣就能夠約束輸入引用的生命週期長度長於結構體實例的生命週期長度。

靜態生命週期參數

前面聊的都是咱們本身定義的生命週期參數,如今來聊聊Rust中內置的生命週期參數'static'static生命週期存活於整個程序運行期間。全部的字符串字面量都有'static生命週期,類型爲&'static str

隱式生命週期參數

在某些狀況下,咱們能夠省略生命週期參數,對於省略的生命週期參數一般有三條規則:

  • 每一個輸入位置上省略的生命週期都將成爲一個不一樣的生命週期參數
  • 若是隻有一個輸入生命週期的位置,則該生命週期將分配給輸出生命週期
  • 若是存在多個輸入生命週期的位置,可是其中包含&self或&mut self,則self的生命週期將分配給輸出生命週期

生命週期限定

生命週期參數也能夠像trait那樣做爲範型的限定

  • T: 'a:表示T類型中的任何引用都要「活得」和'a同樣長
  • T:Trait + 'a:表示T類型必須實現Trait這個trait,而且T類型中的任何引用都要「活得」和'a同樣長

總結

如今我把我對Rust生命週期的瞭解都分享完了。其實只要記住一個原則就能夠了,那就是:生命週期參數的目的是幫助借用檢查器驗證引用的合法性,避免出現懸垂指針

Rust還有幾個深坑,咱們下次繼續。

相關文章
相關標籤/搜索