好了這應該是最後一個比較偏向觀念的地方
有一天,書與報紙只會剩下一個,我會希望的是我與報紙
因為我無法想像她一個人如何對抗孤獨
--- 呱吉
Photo by Fabrizio Verrecchia on Unsplash |
最基本的生命週期(Lifetime)概念
為什麼要有 lifetime,主要是因為有 borrow (reference),但又不希望 dangling我們先來回想一下 borrow
let y; { let x = "OuO"; y = &x; } println!("{}", y);
在上面程式中,y 在 {} 中跟 x 借用了 "OuO"
也就是 y 是指向 x 的 reference
不過會出現以下編譯錯誤
error[E0597]: `x` does not live long enough --> life.rs:5:14 | 5 | y = &x; | ^ borrowed value does not live long enough 6 | } | - `x` dropped here while still borrowed 7 | println!("{}", y); 8 | } | - borrowed value needs to live until here
應該有很明顯地指出問題,x 在 } 就離開有效範圍
此時 x 結束生命週期,擁有的數值隨之消失 (drop)
但是 y 還在借用,不過借用的東西已經消失,因此產生 dangling reference
而這個不安全的操作被編譯器擋下來了
此例子可看出必須要保證被借用者 (x) 的生命週期必須大於借用者 (y) 才行
此例子沒有特別的解決方式
因為這是寫程式的人的錯,本來就不應該產生任何 dangling reference 或 pointer
Borrow Checker
向剛剛的例子中,Rust 編譯器中會有 Borrow Checker 來確認 lifetime 的操作lifetime 為 'a 的 y 借到生命週期為 'b 的 x,明顯看出 'b 活不過 'a 因此產生錯誤訊息
{ let y; // ---------+-- 'a { // | let x = "OuO"; // -+-- 'b | y = &x; // | | } // -+ | println!("y: {}", y); // | } // ---------+
那這樣總可以了吧:
{ let y; // ---------+-- 'a let x = "OuO"; // -+-- 'b | y = &x; // | | println!("{}", y); // | | } // -+-------+ 'b 比 'a 早結束
答案還是不行,因為 drop 的順序與產生順序相反 ('b 比 'a 早一點點點結束)
所以 x 的值會先 drop,接著才是 y,然後就發現 y 借用的 x 已經不在了
正確要如下
{ let x = "OuO"; // ---------+-- 'b let y = &x; // -+-- 'a | println!("{}", y); // | | } // -+-------+ 'b 比 'a 晚結束
標示 Lifetime
要標示 lifetime 時使用單引號再加小寫的單字 e.g. 'a, 'this_a_lifetime不過大部分都用一個小寫字母描述
通常需要標示的會有 function, struct, trait
函式中的 Lifetime
函式有 borrow 當然也會有 lifetime 的問題對於較簡單的 funcrion 可以省略 lifetime,編譯器對於 lifetime 會自行推論
// implicit fn foo(x: &i32) {} // explicit fn bar<'a>(x: &'a i32) {}
那可以被推論的 "簡單 funcrion" 是怎麼簡單?
Lifetime elision 有以下三個規則:
(以下參數講的是 reference 參數)
1. 所有輸入的參數若省略 lifetime,都會被配上不同的 lifetime
2. 恰一個輸入的參數(有無省略),輸出參數若省略 lifetime 都會與輸入的相同
3. 方法有 &Self 或 &mut Self,輸出參數若省略 lifetime 都與 &Self 的 lifetime 相同
(其他就會出現錯誤訊息)
'static
這是一個特殊的生命週期,代表具有整個程式的生命週期通常會使用在全域或是字串上 (&str)
let x: &'static str = "OuO"; static FIVE: i32 = 5; let x: &'static i32 = &FIVE;
例子
可省略
expanded 為 compiler 接收到 elided 形式會自動擴展fn foo(s: &str); // elided fn foo<'a>(s: &'a str); // expanded
非 reference 就不需要標示 lifetime,因為它們通常會移動所有權或複製
若 struct 內部有 reference 也須標示 lifetime
fn foo(i: u32, s: &str); // elided fn foo<'a>(i: u32, s: &'a str); // expanded
fn get_mut(&mut self) -> &mut T; // elided fn get_mut<'a>(&'a mut self) -> &'a mut T; // expanded
fn args<T: ToCStr>(&mut self, args: &[T]) -> &mut Command; // elided fn args<'a, 'b, T: ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command; // expanded
fn new(buf: &mut [u8]) -> BufWriter; // elided fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a>; // expanded
不可省略
fn get_str() -> &str; // ILLEGAL, no inputs
fn frob(s: &str, t: &str) -> &str; // ILLEGAL, two inputs fn frob<'a, 'b>(s: &'a str, t: &'b str) -> &str; // Expanded: Output lifetime is ambiguous
看看以下函式,功能就是比長度,然後回傳長的字串
fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } }
會出錯:原因就是因為 Lifetime elision 的規則
此時 x, y 會附上不同的 lifetime 'a, 'b 回傳時就不知道要附上哪一個,如下面錯誤訊息
error[E0106]: missing lifetime specifier --> src/main.rs:1:33 | 1 | fn longest(x: &str, y: &str) -> &str { | ^ expected lifetime parameter |
解決方式就是要改成這樣,即不能省略 lifetime 標示
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
<'a> 代表此函式有使用一個 lifetime 'a
後面有參考的參數或回傳值都加上 'a
運作方式就如下圖,使用時必須保證 s、t 的 lifetime 長於 u
沒有留言:
張貼留言