1. 程式人生 > 其它 >《Beginning Rust From Novice to Professional》---讀書隨記(生命週期擴充套件)

《Beginning Rust From Novice to Professional》---讀書隨記(生命週期擴充套件)

Beginning Rust From Novice to Professional

Author: Carlo Milanesi

如果需要電子書的小夥伴,可以留下郵箱,看到了會發送的

Chapter 23 More About Lifetimes

Lifetime Elision

trait Tr {
    fn f(x: &u8) -> &u8;
}

這個返回值就不需要標註生命週期,這時候預設在引數中選一個引數為參考,而這裡只有一個引數

trait Tr {
    fn f(b: bool, x: (u32, &u8)) -> &u8;
}
trait Tr {
    fn f(x: &u8) -> (&u8, &f64, bool, &Vec<String>);
}

這些函式有一個特徵,就是都可以找到唯一可用的生命週期

Lifetime Elision with Object-Oriented Programming

trait Tr {
    fn f(&self, y: &u8) -> (&u8, &f64, bool, &Vec<String>);
}

這時候,有兩個引用入參,那麼前面那個約定當然就不起效了,但是,如果是入參有self

,那麼預設就是使用的self的生命週期,也就是等價於下面:

trait Tr {
    fn f<'a>(&'a self, y: &u8) -> (&'a u8, &'a f64, bool, &'a Vec<String>);
}

也可以對選定的引用覆蓋這類行為。比如說,下面的函式中,第二個返回值與y關聯,第一個和第四個預設是和self關聯

trait Tr {
    fn f<'a>(&self, y: &'a u8) -> (&u8, &'a f64, bool, &Vec<String>);
}

The Need of Lifetime Specifiers for Structs

struct S {
    _b: bool,
    _ri: &i32,
}
let _y: S;
let x: i32 = 12;
_y = S { _b: true, _ri: &x };

上面的程式碼,是錯誤的,其實和函式的檢查方式差不多,因為之前一開始的時候,我們的程式碼是寫在一個塊裡面的,函式內部是一個塊,當然結構體的內部也是一個塊,所以它們的檢查也是差不多的。看程式碼,S結構體內部有一個引用欄位,那麼S的宣告是在_y這一行,然後初始化是在x之後,並且引用了x,很明顯,我們是按照變數的宣告順序釋放物件,所以_y的生命週期是比x的長,所以編譯報錯

struct S {
    _b: bool,
    _ri: &i32,
}

fn create_s(ri: &i32) -> S {
    S { _b: true, _ri: ri }
}

// In application code:
fn main() {
    let _y: S;
    let x: i32 = 12;
    _y = create_s(&x);
}

接著看一個複雜一點的例子,程式碼中,函式雖然沒有直接返回引用,但是返回了一個包含引用的結構體的例項,我們首先從內部開始向外看,S中的引用的生命週期與ri關聯,那麼就函式的簽名和主體部分來看,生命週期不需要標註,這部分是對的,但是視角來到main函式中的時候,我們只需要看create_s的函式簽名,返回值的結果與x的生命週期關聯,也就是說,_y的生命週期應該比x要小,但是很明顯不對,_y先宣告的,所以,編譯報錯

struct S {
    _b: bool,
    _ri: &'static i32,
}

fn create_s(ri: &i32) -> S {
    static ZERO: i32 = 0;
    static ONE: i32 = 1;
    S {
        _b: true,
        _ri: if *ri > 0 { &ONE } else { &ZERO },
    }
}

// In application code:
fn main() {
    let _y: S;
    let x: i32 = 12;
    _y = create_s(&x);
}

然後看一個改良版的程式碼,這次,S的引用的生命週期標註是靜態物件,那麼其實這時候,函式create_s的返回值的生命週期和入參就沒有關聯了,把這個S當作是沒有引用的結構體就可以了,因為內部的引用的生命週期與程式掛鉤

Possible Lifetime Specifiers for Structs

實際上,對於結構的引用欄位的生命週期,Rust編譯器只允許兩種可能性:

  • 這樣的欄位只允許引用靜態物件
  • 這樣的欄位允許引用靜態物件或儘管不是靜態物件,但在整個結構物件之前存在並且比它壽命更長的物件
// In some library code:
struct S<'a> { _b: bool, _ri: &'a i32 }
fn create_s<'b>(ri: &'b i32) -> S<'b> {
    S { _b: true, _ri: ri }
}

// In application code:
fn main() {
    let x: i32 = 12;
    let _y: S;
    _y = create_s(&x);
}

_ri儲存在結構體中,不需要檢查“create_s”函式的主體,也不需要檢查“S”結構體的欄位列表;只要檢查create_s函式的簽名和S的簽名,即在開括號之前的宣告部分就足夠了

Other Uses of Lifetime Specifiers

struct S<'a, T> { 
    b: &'a T 
}

這是錯誤的,編譯報錯:"the parameter type T may not live long enough"

這樣做的原因是泛型型別T可以被包含引用的型別具體化,而這樣的引用可能會導致生命週期錯誤。為了防止它們,編譯器禁止這種語法。

  • 由“T”表示的型別將不包含引用,或者它將只包含對靜態物件的引用;
  • 由“T”表示的型別可以包含對非靜態物件的引用,其中必須指定其生命週期;

第一種:

struct S<'a, T: 'static> { b: &'a T }
let s = S { b: &true };
print!("{}", *s.b);

第二種:

struct S<'a, T: 'a> { b: &'a T }
let s1 = S { b: &true };
let s2 = S { b: &s1 };
print!("{} {}", *s1.b, *s2.b.b);

在第一行中,“T”型別引數被限制為“a”生命週期說明符,這意味著這種型別,無論它是什麼,都可能包含一個引用,該引用借用了已經由該生命週期說明符註釋的相同物件,即整個結構物件本身。

在第二行中,“S”結構體被例項化了,它隱式地指定“bool”為它的“T”型別引數。實際上,這種型別不包含任何引用,因此對於這一行,一個靜態邊界就足夠了,如前面的示例程式所示。

但是在第三行中,“S”結構被例項化,隱式地指定S<bool>作為它的“T”型別引數。實際上,這種型別確實包含一個非靜態引用,因此對於這一行,靜態邊界是不夠的。