這篇教程是現行3個Rust所有權系統之一。所有權系統是Rust最獨特且最引人入勝的特性之一,也是作為Rust開發者應該熟悉的。Rust所追求最大的目標 -- 內存安全,關鍵在于所有權。所有權系統有一些不同的概念,每個概念獨自成章:
* [所有權](http://kaisery.gitbooks.io/rust-book-chinese/content/content/5.8.Ownership%20%E6%89%80%E6%9C%89%E6%9D%83.md),關鍵章節
* [借用](http://kaisery.gitbooks.io/rust-book-chinese/content/content/5.9.References%20and%20Borrowing%20%E5%BC%95%E7%94%A8%E5%92%8C%E5%80%9F%E7%94%A8.md),以及它關聯的特性: "引用" (references)
* 生命周期,你正在閱讀的這個章節
這3章依次互相關聯,你需要完整地閱讀全部3章來對Rust的所有權系統進行全面的了解。
## 原則(Meta)
在我們開始詳細講解之前,這有兩點關于所有權系統重要的注意事項。
Rust注重安全和速度。它通過很多_零開銷抽象_(_zero-cost abstractions_)來實現這些目標,也就是說在Rust中,實現抽象的開銷盡可能的小。所有權系統是一個典型的零開銷抽象的例子。本文提到所有的分析都是**在編譯時完成的**。你不需要在運行時為這些功能付出任何開銷。
然而,這個系統確實有一個開銷:學習曲線。很多Rust初學者會經歷我們所謂的“與借用檢查器作斗爭”的過程,也就是指Rust編譯器拒絕編譯一個作者認為合理的程序。這種“斗爭”會因為程序員關于所有權系統如何工作的基本模型與Rust實現的實際規則不匹配而經常發生。當你剛開始嘗試Rust的時候,你很可能會有相似的經歷。然而有一個好消息:更有經驗的Rust開發者反應,一旦他們適應所有權系統一段時間之后,與借用檢查器的沖突會越來越少。
記住這些之后,讓我們來學習有關生命周期的內容。
## 生命周期
借出一個其它人所有資源的引用可以是很復雜的。例如,想象一下下列操作:
1. 我獲取了一個某種資源的句柄
2. 我借給你了一個引用
3. 我決定不再需要這個資源了,然后釋放了它,這時你仍然持有它的引用
4. 你決定使用這個資源
噢!你的引用指向一個無效的資源。這叫做_懸垂指針_(_dangling pointer_)或者“釋放后使用”,如果這個資源是內存的話。
要修正這個問題的話,我們必須確保第四步永遠也不在第三步之后發生。Rust所有權系統通過一個叫_生命周期_(_lifetime_)的概念來做到這一點,它定義了一個引用有效的作用域。
當我們有一個獲取引用作為參數的函數,我們可以隱式或顯式涉及到引用的生命周期:
~~~
// implicit
fn foo(x: &i32) {
}
// explicit
fn bar<'a>(x: &'a i32) {
}
~~~
`'a`讀作“生命周期a”。技術上講,每一個引用都有一些與之相關的生命周期,不過編譯器在通常情況讓你可以省略它們。在我們講到它之前,讓我們拆開顯式的例子看看:
~~~
fn bar<'a>(...)
~~~
這一部分聲明了我們的生命周期。它說`bar`有一個生命周期,`'a`。如果我們有兩個生命周期,它看起來像這樣:
~~~
fn bar<'a, 'b>(...)
~~~
接著在我們的參數列表中,我們使用了我們命名的生命周期:
~~~
...(x: &'a i32)
~~~
如果我們想要一個`&mut`引用,我們這么做:
~~~
...(x: &'a mut i32)
~~~
如果你對比一下`&mut i32`和`&'a mut i32`,他們是一樣的,只是后者在`&`和`mut i32`之間夾了一個`'a`生命周期。`&mut i32`讀作“一個`i32`的可變引用”,而`&'a mut i32`讀作“一個帶有生命周期'a的i32的可變引用”。
當你處理[結構體](http://kaisery.gitbooks.io/rust-book-chinese/content/content/5.12.Structs%20%E7%BB%93%E6%9E%84%E4%BD%93.md)時你也需要顯式的生命周期:
~~~
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);
}
~~~
如你所見,`struct`也可以有生命周期。跟函數類似的方法,
~~~
struct Foo<'a> {
~~~
聲明一個生命周期,接著
~~~
x: &'a i32,
~~~
使用它。然而為什么這里我們需要一個生命周期呢?因為我們需要確保任何`Foo`的引用不能比它包含的`i32`的引用活的更久。
## 理解作用域(Thinking in scopes)
理解生命周期的一個辦法是想象一個引用有效的作用域。例如:
~~~
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`的作用域之中,所以一切正常。那么如果不是呢?下面的代碼不能工作:
~~~
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`的作用域。不過當我們嘗試`x = &f.x`時,我們讓`x`引用一些將要離開作用域的變量。
命名作用域用來賦予作用域一個名字。有了名字是我們可以談論它的第一步。
## 'static
叫做`static`的作用域是特殊的。它代表其具有一個整個程序的作用域。大部分Rust程序員當他們處理字符串時第一次遇到`'static`:
~~~
let x: &'static str = "Hello, world.";
~~~
基本字符串是`&'static str`類型的因為它的引用一直有效:它們被寫入了最終庫文件的數據段。另一個例子是全局量:
~~~
static FOO: i32 = 5;
let x: &'static i32 = &FOO;
~~~
它在二進制文件的數據段中保存了一個`i32`,而`x`是它的一個引用。
## 生命周期省略(Lifetime Elision)
Rust支持強大的在函數體中的局部類型推斷,不過這在項簽名中是禁止的以便允許只通過項簽名本身推導出類型。然而,為了一些人道原因有第二個非常限制的叫做“生命周期省略”的推斷算法適用于函數簽名。它只基于簽名部分自身推斷而不涉及函數體,只推斷生命周期參數,并且只基于3個易于記憶和無歧義的規則,雖然并不隱藏它涉及到的實際類型因為局部推斷可能會適用于它。
當我們討論生命周期省略的時候,我們使用_輸入生命周期和輸出生命周期_(_input lifetime and output lifetime._)。_輸入生命周期_是關于函數參數的,而_輸出生命周期_是關于函數返回值的。例如,這個函數有一個輸入生命周期:
~~~
fn foo<'a>(bar: &'a str)
~~~
這個有一個輸出生命周期:
~~~
fn foo<'a>() -> &'a str
~~~
這個兩者皆有:
~~~
fn foo<'a>(bar: &'a str) -> &'a str
~~~
這里有3條規則:
* 每一個被省略的函數參數成為一個不同的生命周期參數。
* 如果確實有一個輸入生命周期,不管是否省略,這個生命周期被賦予所有函數返回值中被省略的生命周期。
* 如果這里有多個輸入生命周期,不過它們當中有一個是`&self`或者`&mut self`,`self`的生命周期被賦予所有省略的輸出生命周期。
否則,省略一個輸出生命周期將是一個錯誤。
## 例子
這里有一些省略了生命周期的函數的例子。我們用它們的擴展形式配對了每個省略了生命周期的例子。
~~~
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
~~~
- 前言
- 1.介紹
- 2.準備
- 2.1.安裝Rust
- 2.2.Hello, world!
- 2.3.Hello, Cargo!
- 3.學習Rust
- 3.1.猜猜看
- 3.2.哲學家就餐問題
- 3.3.其它語言中的Rust
- 4.高效Rust
- 4.1.棧和堆
- 4.2.測試
- 4.3.條件編譯
- 4.4.文檔
- 4.5.迭代器
- 4.6.并發
- 4.7.錯誤處理
- 4.8.外部語言接口
- 4.9.Borrow 和 AsRef
- 4.10.發布途徑
- 5.語法和語義
- 5.1.變量綁定
- 5.2.函數
- 5.3.原生類型
- 5.4.注釋
- 5.5.If語句
- 5.6.for循環
- 5.7.while循環
- 5.8.所有權
- 5.9.引用和借用
- 5.10.生命周期
- 5.11.可變性
- 5.12.結構體
- 5.13.枚舉
- 5.14.匹配
- 5.15.模式
- 5.16.方法語法
- 5.17.Vectors
- 5.18.字符串
- 5.19.泛型
- 5.20.Traits
- 5.21.Drop
- 5.22.if let
- 5.23.trait對象
- 5.24.閉包
- 5.25.通用函數調用語法
- 5.26.包裝箱和模塊
- 5.27.`const`和`static`
- 5.28.屬性
- 5.29.`type`別名
- 5.30.類型轉換
- 5.31.關聯類型
- 5.32.不定長類型
- 5.33.運算符和重載
- 5.34.`Deref`強制多態
- 5.35.宏
- 5.36.裸指針
- 6.Rust開發版
- 6.1.編譯器插件
- 6.2.內聯匯編
- 6.3.不使用標準庫
- 6.4.固有功能
- 6.5.語言項
- 6.6.鏈接參數
- 6.7.基準測試
- 6.8.裝箱語法和模式
- 6.9.切片模式
- 6.10.關聯常量
- 7.詞匯表
- 8.學院派研究
- 勘誤