您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“rust生命周期源碼分析”,內(nèi)容詳細,步驟清晰,細節(jié)處理妥當(dāng),希望這篇“rust生命周期源碼分析”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。
生命周期是rust中用來規(guī)定引用的有效作用域。在大多數(shù)時候,無需手動聲明,因為編譯器能夠自動推導(dǎo)。當(dāng)編譯器無法自動推導(dǎo)出生命周期的時候,就需要我們手動標(biāo)明生命周期。生命周期主要是為了避免懸垂引用。
rust的編譯器會使用借用檢查器來檢查我們程序的借用正確性。例如:
#![allow(unused)] fn main() { { let r; { let x = 5; r = &x; } println!("r: {}", r); } }
在編譯期,Rust 會比較兩個變量的生命周期,結(jié)果發(fā)現(xiàn) r 明明擁有生命周期 'a,但是卻引用了一個小得多的生命周期 'b,在這種情況下,編譯器會認為我們的程序存在風(fēng)險,因此拒絕運行。
#![allow(unused)] fn main() { fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } }
執(zhí)行這段代碼,rust編譯器會報錯,它給出的help信息如下:
help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
意思是函數(shù)返回類型是一個借用值,但是無法從函數(shù)的簽名中得知返回值是從x還是y借用的。并且給出了相應(yīng)的修復(fù)代碼。
4 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str | ++++ ++ ++ ++
按照這個提示,我們更改函數(shù)聲明。就會發(fā)現(xiàn)可以順利通過編譯。因此,像這樣的函數(shù),我們無法判斷它是返回x還是y,那么只好手動進行生命周期聲明。上面的提示就是手動聲明聲明周期的語法。
需要注意的是,標(biāo)記的生命周期只是為了取悅編譯器,讓編譯器不要難為我們,它不會改變?nèi)魏我玫膶嶋H作用域。
生命周期的語法是以’開頭,名稱往往是一個單獨的小寫字母。大多數(shù)人用’a來作為生命周期的名稱。如果是引用類型的參數(shù),生命周期會位于&之后,并用空格來將生命周期和參數(shù)分隔開。函數(shù)簽名中的生命周期標(biāo)注和泛型一樣,需要在提前聲明生命周期。例如我們剛才修改過的函數(shù)簽名
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str
該函數(shù)簽名表明對于某些生命周期 'a,函數(shù)的兩個參數(shù)都至少跟 'a 活得一樣久,同時函數(shù)的返回引用也至少跟 'a 活得一樣久。實際上,這意味著返回值的生命周期與參數(shù)生命周期中的較小值一致:雖然兩個參數(shù)的生命周期都是標(biāo)注了 'a,但是實際上這兩個參數(shù)的真實生命周期可能是不一樣的(生命周期 'a 不代表生命周期等于 'a,而是大于等于 'a)。例如:
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); } }
result 的生命周期等于參數(shù)中生命周期最小的,因此要等于 string2 的生命周期,也就是說,result 要活得和 string2 一樣久。如過我們將上面的代碼改變?yōu)槿缦滤尽?/p>
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); }
那么將會導(dǎo)致錯誤,因為編譯器知道string2活不到最后一行打印。而string1可以活到打印,但是編譯器并不知道longest返回的是誰。
函數(shù)的返回值如果是一個引用類型,那么它的生命周期只會來源于:
函數(shù)參數(shù)的生命周期
函數(shù)體中某個新建引用的生命周期
若是后者情況,就是典型的懸垂引用場景:
#![allow(unused)] fn main() { fn longest<'a>(x: &str, y: &str) -> &'a str { let result = String::from("really long string"); result.as_str() } }
上面的函數(shù)的返回值就和參數(shù) x,y 沒有任何關(guān)系,而是引用了函數(shù)體內(nèi)創(chuàng)建的字符串,而函數(shù)結(jié)束的時候會自動釋放result的內(nèi)存,從而導(dǎo)致懸垂指針。這種情況,最好的辦法就是返回內(nèi)部字符串的所有權(quán),然后把字符串的所有權(quán)轉(zhuǎn)移給調(diào)用者:
fn longest<'a>(_x: &str, _y: &str) -> String { String::from("really long string") } fn main() { let s = longest("not", "important"); }
在結(jié)構(gòu)體中使用引用,只要為結(jié)構(gòu)體中的每一個引用標(biāo)注上生命周期即可。
struct ImportantExcerpt<'a> { part: &'a str, } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().expect("Could not find a '.'"); let i = ImportantExcerpt { part: first_sentence, }; }
part引用的first_sentence來自于novel,它的生命周期是main函數(shù),因此這段代碼可以正常工作。
ImportantExcerpt 結(jié)構(gòu)體中有一個引用類型的字段 part,因此需要為它標(biāo)注上生命周期。結(jié)構(gòu)體的生命周期標(biāo)注語法跟泛型參數(shù)語法很像,需要對生命周期參數(shù)進行聲明 <'a>。該生命周期標(biāo)注說明,結(jié)構(gòu)體 ImportantExcerpt 所引用的字符串 str 必須比該結(jié)構(gòu)體活得更久。
編譯器為了簡化用戶的使用,運用了生命周期消除大法。例如:
fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] }
對于 first_word 函數(shù),它的返回值是一個引用類型,那么該引用只有兩種情況:
從參數(shù)獲取
從函數(shù)體內(nèi)部新創(chuàng)建的變量獲取
如果是后者,就會出現(xiàn)懸垂引用,最終被編譯器拒絕,因此只剩一種情況:返回值的引用是獲取自參數(shù),這就意味著參數(shù)和返回值的生命周期是一樣的。道理很簡單,我們能看出來,編譯器自然也能看出來,因此,就算我們不標(biāo)注生命周期,也不會產(chǎn)生歧義。
只不過,消除規(guī)則不是萬能的,若編譯器不能確定某件事是正確時,會直接判為不正確,那么你還是需要手動標(biāo)注生命周期
函數(shù)或者方法中,參數(shù)的生命周期被稱為 輸入生命周期,返回值的生命周期被稱為 輸出生命周期。
1.每一個引用參數(shù)都會獲得獨自的生命周期
例如一個引用參數(shù)的函數(shù)就有一個生命周期標(biāo)注: fn foo<'a>(x: &'a i32),兩個引用參數(shù)的有兩個生命周期標(biāo)注:fn foo<'a, 'b>(x: &'a i32, y: &'b i32), 依此類推。
2.若只有一個輸入生命周期(函數(shù)參數(shù)中只有一個引用類型),那么該生命周期會被賦給所有的輸出生命周期,也就是所有返回值的生命周期都等于該輸入生命周期
例如函數(shù) fn foo(x: &i32) -> &i32,x 參數(shù)的生命周期會被自動賦給返回值 &i32,因此該函數(shù)等同于 fn foo<'a>(x: &'a i32) -> &'a i32
3.若存在多個輸入生命周期,且其中一個是 &self 或 &mut self,則 &self 的生命周期被賦給所有的輸出生命周期。
擁有 &self 形式的參數(shù),說明該函數(shù)是一個 方法,該規(guī)則讓方法的使用便利度大幅提升。
讓我們假裝自己是編譯器,然后看下以下的函數(shù)該如何應(yīng)用這些規(guī)則:
例子1
fn first_word(s: &str) -> &str // 實際項目中的手寫代碼
首先,我們手寫的代碼如上所示時,編譯器會先應(yīng)用第一條規(guī)則,為每個參數(shù)標(biāo)注一個生命周期:
fn first_word<'a>(s: &'a str) -> &str // 編譯器自動為參數(shù)添加生命周期
此時,第二條規(guī)則就可以進行應(yīng)用,因為函數(shù)只有一個輸入生命周期,因此該生命周期會被賦予所有的輸出生命周期:
fn first_word<'a>(s: &'a str) -> &'a str // 編譯器自動為返回值添加生命周期
此時,編譯器為函數(shù)簽名中的所有引用都自動添加了具體的生命周期,因此編譯通過,且用戶無需手動去標(biāo)注生命周期,只要按照 fn first_word(s: &str) -> &str
的形式寫代碼即可。
例子2
fn longest(x: &str, y: &str) -> &str // 實際項目中的手寫代碼
首先,編譯器會應(yīng)用第一條規(guī)則,為每個參數(shù)都標(biāo)注生命周期:
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str
但是此時,第二條規(guī)則卻無法被使用,因為輸入生命周期有兩個,第三條規(guī)則也不符合,因為它是函數(shù),不是方法,因此沒有 &self 參數(shù)。在套用所有規(guī)則后,編譯器依然無法為返回值標(biāo)注合適的生命周期,因此,編譯器就會報錯,提示我們需要手動標(biāo)注生命周期。
例子3
impl<'a> ImportantExcerpt<'a> { fn announce_and_return_part(&self, announcement: &str) -> &str { println!("Attention please: {}", announcement); self.part } }
首先,編譯器應(yīng)用第一規(guī)則,給予每個輸入?yún)?shù)一個生命周期。
impl<'a> ImportantExcerpt<'a> { fn announce_and_return_part<'b>(&'a self, announcement: &'b str) -> &str { println!("Attention please: {}", announcement); self.part } }
需要注意的是,編譯器不知道 announcement 的生命周期到底多長,因此它無法簡單的給予它生命周期 'a,而是重新聲明了一個全新的生命周期 'b。接著,編譯器應(yīng)用第三規(guī)則,將 &self 的生命周期賦給返回值 &str
impl<'a> ImportantExcerpt<'a> { fn announce_and_return_part<'b>(&'a self, announcement: &'b str) -> &'a str { println!("Attention please: {}", announcement); self.part } }
盡管我們沒有給方法標(biāo)注生命周期,但是在第一和第三規(guī)則的配合下,編譯器依然完美的為我們亮起了綠燈。
我們來看下面這個例子。將返回值的生命周期聲明為’b,但是實際返回的是生命周期為’a的self.part。
impl<'a: 'b, 'b> ImportantExcerpt<'a> { fn announce_and_return_part(&'a self, announcement: &'b str) -> &'b str { println!("Attention please: {}", announcement); self.part } }
'a: 'b,是生命周期約束語法,跟泛型約束非常相似,用于說明 'a 必須比 'b 活得久
可以把 'a 和 'b 都在同一個地方聲明(如上),或者分開聲明但通過 where 'a: 'b 約束生命周期關(guān)系,如下:
impl<'a> ImportantExcerpt<'a> { fn announce_and_return_part<'b>(&'a self, announcement: &'b str) -> &'b str where 'a: 'b, { println!("Attention please: {}", announcement); self.part } }
加上這個約束,告訴編譯器’a活的比’b更久,引用’a不會產(chǎn)生懸垂指針(無效引用)。
rust中有一個非常特殊的生命周期,那就是’static,擁有該生命周期的引用可以活的和整個程序一樣久。實際上字符串字面值就擁有’static生命周期,它被硬編碼進rust的二進制文件中。'static生命周期非常強大,隨意使用它相當(dāng)于放棄了生命周期檢查。遇到因為生命周期導(dǎo)致的編譯不通過問題,首先想的應(yīng)該是:是否是我們試圖創(chuàng)建一個懸垂引用,或者是試圖匹配不一致的生命周期,而不是簡單粗暴的用 'static 來解決問題。除非實在遇到解決不了的生命周期標(biāo)注問題,可以嘗試’static生命周期。例如:
fn t() -> &'static str{ "qwert" } fn t() -> &'static str{ "qwert" }
注意,使用’static生命周期的時候,不需要提前聲明。
一個復(fù)雜例子: 泛型,特征約束以及生命周期
use std::fmt::Display; fn longest_with_an_announcement<'a, T>( x: &'a str, y: &'a str, ann: T, ) -> &'a str where T: Display, { println!("Announcement! {}", ann); if x.len() > y.len() { x } else { y } }
例子中,包含了生命周期’a,泛型T以及對T的約束Display(因為我們需要打印ann)。
讀到這里,這篇“rust生命周期源碼分析”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。