這篇指南是 Rust 已經(jīng)存在的三個(gè)所有權(quán)制度之一。這是 Rust 最獨(dú)特和最令人信服的一個(gè)特點(diǎn),Rust 開(kāi)發(fā)人員應(yīng)該相當(dāng)熟悉。所有權(quán)即 Rust 如何實(shí)現(xiàn)其最大目標(biāo)和內(nèi)存安全。這里有幾個(gè)不同的概念,每一個(gè)概念都有它自己的章節(jié):
這三篇文章相關(guān)且有序。如果你想完全的理解所有權(quán)制度,你將需要這三篇文章。
在我們了解細(xì)節(jié)之前,這里有關(guān)于所有權(quán)制度的兩個(gè)重要說(shuō)明需要知道。
Rust 注重安全和速度。它通過(guò)許多‘零成本抽象’來(lái)完成這些目標(biāo),這意味著在 Rust 中,用盡可能少的抽象成本來(lái)保證它們正常工作。所有權(quán)制度是一個(gè)零成本抽象概念的一個(gè)主要例子。我們將在這篇指南中提到的所有分析都是在編譯時(shí)完成的。你不用為了任何功能花費(fèi)任何運(yùn)行成本。
然而,這一制度確實(shí)需要一定的成本:學(xué)習(xí)曲線。許多新用戶(hù)使用我們喜歡稱(chēng)之為‘與借檢查器的人斗爭(zhēng)’,即 Rust 編譯器拒絕編譯那些作者認(rèn)為有效的程序的 Rust 經(jīng)驗(yàn)。這往往因?yàn)槌绦騿T對(duì)于所有權(quán)的怎樣工作與 Rust 實(shí)現(xiàn)的規(guī)則不相符的心理模型而經(jīng)常出現(xiàn)。你可能在第一次時(shí)會(huì)經(jīng)歷類(lèi)似的事情。然而有個(gè)好消息:更有經(jīng)驗(yàn)的 Rust 開(kāi)發(fā)者報(bào)告稱(chēng),一旦他們遵從所有權(quán)制度的規(guī)則工作一段時(shí)間后,他們會(huì)越來(lái)越少的與借檢查器的行為斗爭(zhēng)。
學(xué)習(xí)了這些后,讓我們來(lái)了解生存期。
借出一個(gè)對(duì)于其他人已經(jīng)擁有的資源的引用會(huì)很復(fù)雜。例如,假設(shè)這一系列的操作:
我獲得某種資源的一個(gè)句柄。
我借給你對(duì)于這個(gè)資源的引用。
我決定我使用完了這個(gè)資源,然后釋放它,然而你仍然擁有這個(gè)引用。
你的引用指向一個(gè)無(wú)用的資源。當(dāng)資源是內(nèi)存時(shí),這被稱(chēng)為懸掛指針或者‘釋放后再利用’。
要解決此類(lèi)問(wèn)題,我們必須確保在第三步后一定不會(huì)發(fā)生第四步。Rust 的所有權(quán)制度通過(guò)被稱(chēng)為生存期的一章來(lái)實(shí)現(xiàn),生存期用來(lái)介紹一個(gè)引用的有效的作用域。
當(dāng)我們有一個(gè)函數(shù)將一個(gè)引用作為參數(shù)時(shí),我們可以用隱式和顯式兩種方式來(lái)表示引用的生存期:
// implicit
fn foo(x: &i32) {
}
// explicit
fn bar<'a>(x: &'a i32) {
}
'a 讀‘生存期為 a ’。從技術(shù)上講,每個(gè)引用都有一些與之關(guān)聯(lián)的生存期,但是編譯器允許你在一般情況下忽略它們。但是在此之前,以下是我們分解顯式例子的代碼:
fn bar<'a>(...)
這一部分聲明了我們的生存期。這說(shuō)明 bar 有一個(gè)生存期 'a。如果我們有兩個(gè)引用的參數(shù),以下是相關(guān)代碼:
fn bar<'a, 'b>(...)
然后在我們的參數(shù)列表中,我們使用我們已經(jīng)命名的生存期。
...(x: &'a i32)
如果我們想要一個(gè) &mut 引用,我們可以書(shū)寫(xiě)以下代碼:
...(x: &'a mut i32)
如果你將 &mut i32 和 &'a mut i32 比較,它們是相同的,只是在 & 和 mut i32 之間多了一個(gè) 'a。我們將 &mut i32 讀作‘對(duì)于 i32 的一個(gè)可變引用’,將 &'a mut i32讀作‘對(duì)于 i32 的一個(gè)生存期為'a 的一個(gè)可變引用’。
當(dāng)你操作結(jié)構(gòu)體時(shí),也需要顯式的生存期:
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let y = &5; // this is the same as `let _y = 5; let y = &_y;`
let f = Foo { x: y };
println!("{}", f.x);
}
正如你所看到的的,結(jié)構(gòu)體也可以有生存期。與函數(shù)相似的方式,
struct Foo<'a> {
聲明一個(gè)生存期,和以下代碼
x: &'a i32,
使用它。所以,為什么我們?cè)谶@里需要一個(gè)生存期?我們需要確保對(duì) Foo 的任何引用都不能比對(duì)它包含的 i32 的引用的壽命長(zhǎng)。
考慮生存期的一種方式是將一個(gè)引用的有效作用域可視化。例如:
fn main() {
let y = &5; // -+ y goes into scope
// |
// stuff// |
// |
} // -+ y goes out of scope
在我們的 Foo 中添加:
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let y = &5; // -+ y goes into scope
let f = Foo { x: y }; // -+ f goes into scope
// stuff // |
// |
} // -+ f and y go out of scope
我們的 f 在 y 的作用域內(nèi)存活,所以一切正常。如果它不是呢?這個(gè)代碼不會(huì)有效工作:
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let x;// -+ x goes into scope
// |
{ // |
let y = &5; // ---+ y goes into scope
let f = Foo { x: y }; // ---+ f goes into scope
x = &f.x; // | | error here
} // ---+ f and y go out of scope
// |
println!("{}", x);// |
} // -+ x goes out of scope
正如你所看到的,f 和 y 的作用域比 x 的作用域要小。但是,當(dāng)我們運(yùn)行 x = &f.x 時(shí),我們給了 x 一個(gè)可以超出其作用域范圍的引用。
命名生存期是給這些作用域命名的一種方式。給東西命名是能不能討論它的第一步。
命名為 ‘static’ 的生存期是一個(gè)特殊的生存期。這標(biāo)志著這種東西具有整個(gè)程序的生存期。很多的 Rust 程序員在處理字符串時(shí)會(huì)第一次遇到 'static:
let x: &'static str = "Hello, world.";
字符串具有 &'static str 這種類(lèi)型,是因?yàn)檫@種引用始終存在:它們?nèi)谌氲阶罱K二進(jìn)制的數(shù)據(jù)段中。另一個(gè)例子是全局變量:
static FOO: i32 = 5;
let x: &'static i32 = &FOO;
以上代碼是將 i32 加入到二進(jìn)制文件的數(shù)據(jù)段中,其中 x 是它的一個(gè)引用。
Rust 在函數(shù)體中支持強(qiáng)大的局部類(lèi)型推斷,但是它在項(xiàng)目簽名中禁止允許僅僅基于單獨(dú)的項(xiàng)目簽名中的類(lèi)型推斷。然而,對(duì)于人體工學(xué)推理來(lái)說(shuō)被稱(chēng)為“生存期省略”的一個(gè)非常受限的二級(jí)推理算法,對(duì)于函數(shù)簽名非常適用。它能推斷僅僅基于簽名組件本身而不是基于函數(shù)體,僅推斷生存期參數(shù),并且通過(guò)僅僅三個(gè)容易記住和明確的規(guī)則來(lái)實(shí)現(xiàn),這使得生存期省略成為一個(gè)項(xiàng)目簽名的縮寫(xiě),而不是引用它之后隱藏包含完整的本地推理的實(shí)際類(lèi)型。
當(dāng)我們談到生存期省略時(shí),我們使用生存期輸入和生存期輸出 這兩個(gè)術(shù)語(yǔ)。生存期輸入是與一個(gè)函數(shù)的一個(gè)參數(shù)結(jié)合的一個(gè)生存期,同時(shí)一個(gè)生存期輸出是一個(gè)與函數(shù)的返回值相結(jié)合的一個(gè)生存期。例如,以下函數(shù)有一個(gè)生存期輸入:
fn foo<'a>(bar: &'a str)
以下函數(shù)有一個(gè)生存期輸出:
fn foo<'a>() -> &'a str
以下函數(shù)在兩個(gè)位置都有一個(gè)生存期:
fn foo<'a>(bar: &'a str) -> &'a str
這里有三個(gè)規(guī)定:
在函數(shù)參數(shù)中每個(gè)省略的生存期都成為一個(gè)獨(dú)特的生存期參數(shù)。
如果僅僅有一個(gè)輸入生存期,省略或者不省略,在這個(gè)函數(shù)的返回值中,這個(gè)生存期被分配給所有的省略的生存期。
另外,省略一個(gè)生存期輸出是錯(cuò)誤的。
以下列舉了生存期省略的函數(shù)的一些例子。我們已經(jīng)將每個(gè)生存期省略的例子和它的擴(kuò)展形式進(jìn)行了配對(duì)。
fn print(s: &str); // elided
fn print<'a>(s: &'a str); // expanded
fn debug(lvl: u32, s: &str); // elided
fn debug<'a>(lvl: u32, s: &'a str); // expanded
// In the preceding example, `lvl` doesn’t need a lifetime because it’s not a
// reference (`&`). Only things relating to references (such as a `struct`
// which contains a reference) need lifetimes.
fn substr(s: &str, until: u32) -> &str; // elided
fn substr<'a>(s: &'a str, until: u32) -> &'a str; // 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 unclear
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
更多建議: