# Rust 生命周期 (Lifetime)
:::success
您正在閱讀「[Rust Taiwan 2020 讀書會筆記](https://hackmd.io/@ballfish/Hy7jJN7WI)」的一節,歡迎點擊本頁上方的 <i class="fa fa-pencil btn btn-default disabled"></i> 協助編修。
:::
## 介紹(幹話)
- ==變數== 從出生到死亡的時間段
```rust=
fn main () {
let x = Box::new(5); // x 出生
println!("{:?}", x);
{
let y = Box::new(1); // y 出生
println!("{:?}", y);
} // y 死亡
// cannot find value `y` in this scope
// y 死掉了,所以你存取不到他
println!("{:?}", y);
} // x 死亡
```
## Borrow checker
- 編譯器的機制
- 會檢查借用者的生命周期會不會活的比擁有者久
- 為了避免 null pointer 發生,就是擁有者已經死了,Value 已經被銷毀了,但借用者還活著,就會存取到不存在的東西
```rust=
#[allow(unused_variables, unused_assignments)]
fn main () {
let x; // x 出生
{
let y = Box::new(1); // y 出生
x = &y; // x 借用 y 的所有權
println!("{:?}", y);
} // y 死亡
// `y` does not live long enough
// y 死掉了,所以 x 存取不到他 (1編譯器:可憐的 y 他活的不夠久 owo)
println!("{:?}", x);
} // x 死亡
```
## 生命周期標示
- 在名字前面加個 `'` ,就是生命周期的標示
- 以剛剛的例子來說
```rust=
fn main () {
test();
}
// 生命周期標示,必須像泛型一樣,在 function 簽名中先被宣告
fn test<'a, 'b> () {
let x: &'a i32 = &5; // 'a 開始
println!("{:?}", x);
{
let y: &'b i32 = &2; // 'b 開始
println!("{:?}", y);
} // 'b 結束
} // 'a 結束
```
- 不必要標生命周期的情況
```rust=
#[derive(Debug)]
struct Person {
age: i32
}
// 因為 傳入值 與 回傳值 只有一個
// 不會造成編譯器需要檢查生命周期的問題
// 所以沒有必要標示生命周期
fn life_again_gun (y: &mut Person) -> &mut Person {
y.age = 0;
y
}
fn main () {
let mut x = Person { age: 16 };
let y = life_again_gun(&mut x);
println!("{:?}", y);
}
```
- 必須要標生命周期的情況
```rust=
#[derive(Debug)]
struct Person {
age: i32
}
// missing lifetime specifier
// 因為編譯器看不出回傳的 借用者 是不是會超過 擁有者 的 lifetime
// 所以要求你編上 lifetime
fn the_older (x: &Person, y: &Person) -> &Person {
if x.age > y.age { x } else { y }
}
fn main () {
}
```
```rust=
#[derive(Debug)]
struct Person {
age: i32
}
// 我們預期這裡只會有一種生命周期
fn the_older<'a> (x: &'a Person, y: &'a Person) -> &'a Person {
if x.age > y.age { x } else { y }
}
fn main () {
let x = Person { age: 16 };
let y = Person { age: 17 };
let res = the_older(&x, &y);
println!("{:?}", res)
}
```
- 指定多個生命周期,並標示哪個生命周期比較長
```rust=
#[derive(Debug)]
struct Person {
age: i32
}
// 我們有兩個生命周期 'a 與 'b,其中 'b 活的比 'a 久
fn the_older<'a, 'b: 'a> (x: &'a Person, y: &'b Person) -> &'a Person {
if x.age > y.age { x } else { y }
}
fn main () {
let x = Person { age: 16 };
let res;
{
let y = Person { age: 17 };
res = the_older(&x, &y);
println!("{:?}", res);
}
}
```
## NLL (Non-Lexical-Lifetime)
### Lexical-Lifetime
- 是指說生命周期與變數的作用域是綁定在一起的
- 舉個例子
```rust=
#[derive(Debug)]
struct Person {
age: i32
}
fn birthday (y: &mut Person) {
y.age = y.age + 1;
}
fn life_again_gun (y: &mut Person) -> &mut Person {
y.age = 0;
y
}
fn main () {
let mut x = Person { age: 16 };
let y = life_again_gun(&mut x);
// 在 Lexical-Lifetime 的情況,y 的生命周期沒有結束
// 所以 y 還在進行可變借用
// 那理論上 x 就不可以再度可變出借
// (NLL 好像已經是標準了,所以我無法實現 LL 的編譯錯誤)
birthday(&mut x);
println!("{:?}", x);
}
```
### Non-Lexical-Lifetime
- borrow checker 的分析結構方式從 AST 轉向 MIR
- AST 是抽象語法樹,它會以樹狀的形式表現程式語言的語法結構,因為舊的 borrow checker 用 AST 做分析,所以會造成生命周期與作用域掛鉤
- MIR 是中間表達式,他在編譯器內部會有像是流程圖的資料結構,用流程控制的方式去分析生命周期
- 只要變數在後面的程式碼中,沒有機會被使用到,就會提早被結束生命周期
- NLL 將作用域與生命周期拆開來看了
- NLL 縮短了過長的生命周期 (縮減了變數的生命),讓程式不會充滿一堆 block 去迴避 LL 造成的問題
- 舉例來說
```rust=
#[derive(Debug)]
struct Person {
age: i32
}
fn birthday (y: &mut Person) {
y.age = y.age + 1;
}
fn life_again_gun (y: &mut Person) -> &mut Person {
y.age = 0;
y
}
fn main () {
let mut x = Person { age: 16 };
let y = life_again_gun(&mut x);
// 在 Non-Lexical-Lifetime 的情況
// y 在這段程式碼的後面都沒有被使用到
// y 的生命周期就結束了
// 那這裡就不會有問題
birthday(&mut x);
println!("{:?}", x);
}
```
```rust=
#[derive(Debug)]
struct Person {
age: i32
}
fn birthday (y: &mut Person) {
y.age = y.age + 1;
}
fn life_again_gun (y: &mut Person) -> &mut Person {
y.age = 0;
y
}
fn main () {
let mut x = Person { age: 16 };
let y = life_again_gun(&mut x);
// cannot borrow `x` as mutable more than once at a time
// 但如果 y 在後面有機會被使用到
// 就代表 y 的生命周期還沒有結束
// 所以 x 不可以再度進行可變出借
birthday(&mut x);
y.age = 16;
println!("{:?}", x);
}
```