記錄一下自己理解的生命周期。
每個變量都有自己的生命周期。
在c++里生命周期好比作用域, 小的作用域的可以使用大作用域的變量。 如果把這里的每個作用域取個名,那么就相當於rust里的生命周期注解。
拿例子說事一:
如果按照c++的方式來理解, 這個x和r的作用域是一樣的,都是在main函數中。
但是如果按照rust生命周期去理解, x的生命周期比r的生命周期大。 因為a在main的第一行. b的在main中的第二行。
有變量的生命周期是一樣的,那就是元組結構. 類似這樣。 let (a,b,c) = tuple;
rust不允許, let a=1,b=1; 這樣定義變量(c/c++/js風格)
也不允許 let a,b=4,1; 這樣定義變量, (lua風格)
在這里其實'a 和 'b的生命周期 在編譯器看來是不一樣的,這一點一定要記住。 但是可以調用同一生命周期的函數,因為 'b生命周期包含'a 生命周期。
編譯器處理其實是按照'a生命周期(公共的部分,也可以理解為使用較短生命周期)代入函數中的。 某個變量的生命周期是開始定義變量開始,到最后一次使用變量。 這里可能設計變量租借延續生命周期。
拿例子說事二:
fn main() { let string1 = String::from("abcd"); let string2 = "xyz"; let result = longest(string1.as_str(), string2); println!("The longest string is {}", result); } fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } }
如果用c++的視角(編碼風格)去看這個程序, 會覺得是一段很正常的代碼,完全沒毛病啊。
但是實際編譯就會出錯,錯誤提示就是和生命周期有關。
對技術這么嚴謹的我,內心問了一萬個為什么?
我經常把自己想成編譯器作者,自己問自己,如果你是作者你會怎么處理?
答: 每個函數都會檢查每個變量的生命周期的范圍。 對longest函數來說, 有兩個參數,x,y 都是都是引用。 本身不占空間(4或8字節指針忽略)。
在編譯longest的時候,x,y引用誰,編譯器不清楚。 有一種辦法是可以知道的,就是編譯完整個rust項目代碼,在回過頭看這個。就可以知道了。
但是這種不現實,這種會造成編譯器及其復雜,並且及其緩慢。 因為一個項目不只有只有一個loggest, 有成千上萬類似這樣的,互相交錯調用。
得來回反復的編譯檢查才能確定引用的誰,生命周期如何,這種是不確定的,x可能引用的東東生命周期長,可能短, 如果把函數結果賦值給屬於其他生命周期的變量,就可能存在安全隱患了,c++是可以這么干的,但是開發者必須清楚指針指向的哪,引用的誰,否則程序崩了,不要怪誰,就是開發者垃圾。但是事實上,再牛逼的程序員,也會寫出有bug的程序。
rust就是為安全而生的, 編譯器幫你排除內存隱患,並發隱患。完成這個目標可不容易,需要開發者協助完成( 導致學習難度有點大,主要卡在思維上。 開發者不要隨心所欲)
說了這么多,這個核心問題就是:
粗魯點解釋: 老子(rustc)不知道你指向的東西,和你的函數結果要返回給哪個,不告訴我,老子我就不讓你過。
專業點解釋: 因為rustc不知道 x
和 y
的生命周期是如何與返回值的生命周期相關聯的
開發者獨白: 老子該怎么告訴你(rustc)啊?
答:增加泛型生命周期參數來定義引用間的關系以便借用檢查器可以進行分析
生命周期注解語法
生命周期注解並不改變任何引用的生命周期的長短。與當函數簽名中指定了泛型類型參數后就可以接受任何類型一樣,當指定了泛型生命周期后函數也能接受任何生命周期的引用。生命周期注解描述了多個引用生命周期相互的關系,而不影響其生命周期。
生命周期注解有着一個不太常見的語法:生命周期參數名稱必須以撇號('
)開頭,其名稱通常全是小寫,類似於泛型其名稱非常短。'a
是大多數人默認使用的名稱。生命周期參數注解位於引用的 &
之后,並有一個空格來將引用類型與生命周期注解分隔開。
這里有一些例子:我們有一個沒有生命周期參數的 i32
的引用,一個有叫做 'a
的生命周期參數的 i32
的引用,和一個生命周期也是 'a
的 i32
的可變引用:
&i32 // 引用 &'a i32 // 帶有顯式生命周期的引用 &'a mut i32 // 帶有顯式生命周期的可變引用
單個的生命周期注解本身沒有多少意義,因為生命周期注解告訴 Rust 多個引用的泛型生命周期參數如何相互聯系的。例如如果函數有一個生命周期 'a
的 i32
的引用的參數 first
。還有另一個同樣是生命周期 'a
的 i32
的引用的參數 second
。這兩個生命周期注解意味着引用 first
和 second
必須與這泛型生命周期存在得一樣久。
注解只是一個名字,用單引號和標識符組成.
函數簽名中的生命周期注解
就像泛型類型參數,泛型生命周期參數需要聲明在函數名和參數列表間的尖括號中。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
現在函數簽名表明對於某些生命周期 'a
,函數會獲取兩個參數,他們都是與生命周期 'a
存在的一樣長的字符串 slice。函數會返回一個同樣也與生命周期 'a
存在的一樣長的字符串 slice。這就是我們告訴 Rust 需要其保證的契約。記住通過在函數簽名中指定生命周期參數時,我們並沒有改變任何傳入后返回的值的生命周期。而是指出任何不遵守這個協議的傳入值都將被借用檢查器拒絕。注意 longest
函數並不需要知道 x
和 y
具體會存在多久,而只需要知道有某個可以被 'a
替代的作用域將會滿足這個簽名。
當在函數中使用生命周期注解時,這些注解出現在函數簽名中,而不存在於函數體中的任何代碼中。這是因為 Rust 能夠分析函數中代碼而不需要任何協助,不過當函數引用或被函數之外的代碼引用時,讓 Rust 自身分析出參數或返回值的生命周期幾乎是不可能的。這些生命周期在每次函數被調用時都可能不同。這也就是為什么我們需要手動標記生命周期。
當具體的引用被傳遞給 longest
時,被 'a
所替代的具體生命周期是 x
的作用域與 y
的作用域相重疊的那一部分。換一種說法就是泛型生命周期 'a
的具體生命周期等同於 x
和 y
的生命周期中較小的那一個。因為我們用相同的生命周期參數 'a
標注了返回的引用值,所以返回的引用值就能保證在 x
和 y
中較短的那個生命周期結束之前保持有效。
以下代碼正確:
fn main() { let string1 = String::from("long string is long"); { let string2 = String::from("xyz"); let result = longest(string1.as_str(), string2.as_str()); println!("The longest string is {}", result); } } fn longest<'a>(x:&'a str, y:&'a str) -> &'a str{ if x.len()>y.len() { x }else{ y } }
以下代碼錯誤:
fn main() { let string1 = String::from("long string is long"); let result; { let string2 = String::from("xyz"); result = longest(string1.as_str(), string2.as_str()); } println!("The longest string is {}", result); } fn longest<'a>(x:&'a str, y:&'a str) -> &'a str{ if x.len()>y.len() { x }else{ y } }
失敗原因:
通過生命周期參數告訴 Rust 的是: longest
函數返回的引用的生命周期應該與傳入參數的生命周期中較短那個保持一致。
因此,借用檢查器不允許示例 10-24 中的代碼,因為它可能會存在無效的引用。
longest函數結果的表明的生命周期應該與string2一致。 但是代碼反饋的是result與string1一致。
可以使用租借(給string2續命,不要立即釋放),修復錯誤:
fn main() { let string1 = String::from("long string is long"); let result; let zujie; { let string2 = String::from("xyz"); zujie = string2; result = longest(string1.as_str(), zujie.as_str()); } println!("The longest string is {}", result); }
如果返回值和y沒有關系,可以不用對y注解。
fn longest<'a>(x: &'a str, y: &str) -> &'a str { x }
出現垂懸引用,代碼不通過。 說明:這個可以通過使用有所有權的變量租借解決此問題。說白點,就是給返回值續命。 如果真這樣解決,就不需要生命周期注解了。
fn longest<'a>(x: &str, y: &str) -> &'a str { let result = String::from("really long string"); result.as_str() }
綜上,生命周期語法是用於將函數的多個參數與其返回值的生命周期進行關聯的。一旦他們形成了某種關聯,Rust 就有了足夠的信息來允許內存安全的操作並阻止會產生懸垂指針亦或是違反內存安全的行為。