# 生命周期
> [lifetimes.md](https://github.com/rust-lang/rust/blob/master/src/doc/book/lifetimes.md)
commit f4fac9b0fa55d253b438eccdf1794baace6c9efe
這篇教程是現行 3 個 Rust 所有權系統之一。所有權系統是 Rust 最獨特且最引人入勝的特性之一,也是作為 Rust 開發者應該熟悉的。Rust 所追求最大的目標 -- 內存安全,關鍵在于所有權。所有權系統有一些不同的概念,每個概念獨自成章:
- [所有權](#),關鍵章節
- [借用](#),以及它關聯的特性: "引用" (references)
- 生命周期,你正在閱讀的這個章節
這 3 章依次互相關聯,你需要完整地閱讀全部 3 章來對 Rust 的所有權系統進行全面的了解。
### 原則(Meta)
在我們開始詳細講解之前,這有兩點關于所有權系統重要的注意事項。
Rust 注重安全和速度。它通過很多*零開銷抽象*(*zero-cost abstractions*)來實現這些目標,也就是說在 Rust 中,實現抽象的開銷盡可能的小。所有權系統是一個典型的零開銷抽象的例子。本文提到所有的分析都是**在編譯時完成的**。你不需要在運行時為這些功能付出任何開銷。
然而,這個系統確實有一個開銷:學習曲線。很多 Rust 初學者會經歷我們所謂的“與借用檢查器作斗爭”的過程,也就是指 Rust 編譯器拒絕編譯一個作者認為合理的程序。這種“斗爭”會因為程序員關于所有權系統如何工作的基本模型與 Rust 實現的實際規則不匹配而經常發生。當你剛開始嘗試 Rust 的時候,你很可能會有相似的經歷。然而有一個好消息:更有經驗的 Rust 開發者反映,一旦他們適應所有權系統一段時間之后,與借用檢查器的沖突會越來越少。
記住這些之后,讓我們來學習有關生命周期的內容。
### 生命周期
借出一個其它人所有資源的引用可以是很復雜的。例如,想象一下下列操作:
- 我獲取了一個某種資源的句柄
- 我借給你了一個關于這個資源的引用
- 我決定不再需要這個資源了,然后釋放了它,這時你仍然持有它的引用
- 你決定使用這個資源
噢!你的引用指向一個無效的資源。這叫做*懸垂指針*(*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的可變引用”。
### 在`struct`中
當你處理[結構體](#)時你也需要顯式的生命周期:
~~~
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,
# }
~~~
聲明一個生命周期,接著
~~~
# struct Foo<'a> {
x: &'a i32,
# }
~~~
使用它。然而為什么這里我們需要一個生命周期呢?因為我們需要確保任何`Foo`的引用不能比它包含的`i32`的引用活的更久。
### `impl`塊
讓我們在`Foo`中實現一個方法:
~~~
struct Foo<'a> {
x: &'a i32,
}
impl<'a> Foo<'a> {
fn x(&self) -> &'a i32 { self.x }
}
fn main() {
let y = &5; // this is the same as `let _y = 5; let y = &_y;`
let f = Foo { x: y };
println!("x is: {}", f.x());
}
~~~
如你所見,我們需要在`impl`行為`Foo`聲明一個生命周期。我們重復了`'a`兩次,就像在函數中:`impl<'a>`定義了一個生命周期`'a`,而`Foo<'a>`使用它。
### 多個生命周期
如果你有多個引用,你可以多次使用同一個生命周期:
~~~
fn x_or_y<'a>(x: &'a str, y: &'a str) -> &'a str {
# x
# }
~~~
這意味著`x`和`y`存活在同樣的作用域內,并且返回值也同樣存活在這個作用域內。如果你想要`x`和`y`有不同的生命周期,你可以使用多個生命周期參數:
~~~
fn x_or_y<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
# x
# }
~~~
在這個例子中,`x`和`y`有不同的有效的作用域,不過返回值和`x`有相同的生命周期
### 理解作用域(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 ambiguous
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.準備
- 3.學習 Rust
- 3.1.猜猜看
- 3.2.哲學家就餐問題
- 3.3.其它語言中的 Rust
- 4.語法和語義
- 4.1.變量綁定
- 4.2.函數
- 4.3.原生類型
- 4.4.注釋
- 4.5.If語句
- 4.6.循環
- 4.7.所有權
- 4.8.引用和借用
- 4.9.生命周期
- 4.10.可變性
- 4.11.結構體
- 4.12.枚舉
- 4.13.匹配
- 4.14.模式
- 4.15.方法語法
- 4.16.Vectors
- 4.17.字符串
- 4.18.泛型
- 4.19.Traits
- 4.20.Drop
- 4.21.if let
- 4.22.trait 對象
- 4.23.閉包
- 4.24.通用函數調用語法
- 4.25.crate 和模塊
- 4.26.const和static
- 4.27.屬性
- 4.28.type別名
- 4.29.類型轉換
- 4.30.關聯類型
- 4.31.不定長類型
- 4.32.運算符和重載
- 4.33.Deref強制多態
- 4.34.宏
- 4.35.裸指針
- 4.36.不安全代碼
- 5.高效 Rust
- 5.1.棧和堆
- 5.2.測試
- 5.3.條件編譯
- 5.4.文檔
- 5.5.迭代器
- 5.6.并發
- 5.7.錯誤處理
- 5.8.選擇你的保證
- 5.9.外部函數接口
- 5.10.Borrow 和 AsRef
- 5.11.發布途徑
- 5.12.不使用標準庫
- 6.Rust 開發版
- 6.1.編譯器插件
- 6.2.內聯匯編
- 6.4.固有功能
- 6.5.語言項
- 6.6.鏈接進階
- 6.7.基準測試
- 6.8.裝箱語法和模式
- 6.9.切片模式
- 6.10.關聯常量
- 6.11.自定義內存分配器
- 7.詞匯表
- 8.語法索引
- 9.參考文獻
- 附錄:名詞中英文對照