《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”型別引數。實際上,這種型別確實包含一個非靜態引用,因此對於這一行,靜態邊界是不夠的。