iT邦幫忙

2022 iThome 鐵人賽

DAY 5
0

Lifetime

上一篇有提到值的存活時間,因為Rust在一個作用域結束後,會自動Drop所有在內的變數,所以在編寫程式碼時有使用到引用就需要非常注意變數的存活時間。

fn main() {
    let x: &Vec<i32>;
    {
        let y = vec![111];

        x = &y;// y doesn't live long enough
    }// y drop here

    println!("{:?}", x);// borrow later used here
}

為什麼需要標注生命週期

在其他語言當中,stack或是heap中數據的存活時間是不固定的,因此會需要開發者手動維護(C)或是程式執行時額外做處理(Garbage collection)。在Rust當中,heap中數據的存活時間預設跟stack中的數據是一樣的,因此在同個作用域底下編譯器可以清楚知道被引用的存活時間有沒有大於引用

當一個函式有使用到引用的參數時,編譯器為了確保被引用的值的存活時間大於等於引用的值,會要求開發者手動標注參數生命週期。

fn main() {
    let s1 = String::from("first");
    let s2 = String::from("second");

    let res = max(&s1, &s2);

    println!("{}", res);
}

fn max(s1: &str, s2: &str) -> &str { // missing lifetime specifier
    if s1 > s2 {
        s1
    } else {
        s2
    }
}

上述程式碼fn max出現編譯器的提示需要標注生命週期,那為什麼會這樣呢?因為編譯器不知道res這個變數引用的到底是s1還是s2,那編譯器就無從去檢查s1s2是不是活得比res還要久。

fn main() {
    let s1 = String::from("first");
    let s2 = String::from("second");

    let res = max(&s1, &s2);

    println!("{}", res);
}

fn max<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1 > s2 {
        s1
    } else {
        s2
    }
}

我們將max補上<'a>代表這個函式會用到標示為a的生命週期,s1s2&'a str則代表這兩的變數活的時間都是a,回傳的&'a str則代表的是res會引用到的變數的生命週期。

做個小結論,只有參數為引用的才需要標示生命週期。回傳的值若標示為'a那麼傳入的參數的存活時間就必須大於'a,反過來說就是s1s2變數的存活時間必須比res還要長。

fn main() {
    let s1 = String::from("first");
    let s2 = String::from("second");

    let res = max(&s1, &s2);

    println!("{}", res);
}

fn max<'a, 'b>(s1: &'a str, s2: &'b str) -> &'a str {
    if s1.len() > s2.len() {
        s1
    } else {
        s2 // lifetime may not live long enough, consider adding the following bound: `'b:a`
    }
}

當然我們也可以兩個傳入的參數分別用不同兩個記號標記,但這時會看到編譯器建議我們在標注'b後面加上a,這個意思就是標示b的存活時間與a一樣長,那就符合剛剛說的參數的生命週期必須大於等於回傳的生命週期 b必須大於等於a

省略標記生命週期

fn main() {
    let s1 = String::from("first");
    print_string(&s1)
}

fn print_string(s1: &str) {
    println!("{}", s1);
}

但從上述的例子可以發現,有些函式即使有用到引用的參數但我們也不必去標記生命週期,原因是因為Rust在最初版本中是需要每個引用都標記的,但Rust團隊發現,在某些情況下生命週期的標記其實是有規律的,因此只要符合照些條件在編譯的過程中Rust都會幫我們自動標上

  1. 每個引用都有自己的生命週期('a, 'b)
  2. 只有一個引用的參數
  3. 如果有多個引用參數但其中一個是**&self或是mut self**

靜態生命週期

若我們需要一個變數的值貫穿整個程式(process),常見的全域變數就是中之一,我們稱為靜態生命週期

let s1: &'static str = "hello";

只需要在將標記的文字改成static即可。


上一篇
[D4] Ownership
下一篇
[D6] Borrow
系列文
大閘蟹料理指南(rust)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言