所有權(quán)
垃圾回收管理內(nèi)存
Python,Java這類語言在管理內(nèi)存時引用了一種叫做垃圾回收的技術(shù),這種技術(shù)會為每個變量設(shè)置一個引用計數(shù)器(reference counter),來統(tǒng)計每個對象的引用次數(shù)。
一旦某個對象的引用數(shù)為0,垃圾回收器就會擇取一個時機(jī)將其所占用的空間回收。
以Python為例子
x = [1, 2] # 列表對象[1, 2]被x引用,引用計數(shù)為1
y = x # 列表對象[1, 2]被y引用,引用計數(shù)為2
del x # 列表對象[1, 2]的引用x被刪除,此時并不刪除對象,引用計數(shù)為1
del y # 列表對象[1, 2]的引用y被刪除,此時并不立即刪除對象,引用計數(shù)為0
# 但此對象將會在下一次垃圾回收時被清理
這種垃圾回收的方式很好的避免了開發(fā)者去管理內(nèi)存,但是也造成了一些列問題。也就是:
- 這種垃圾回收不會立刻進(jìn)行,而是會選取某個時機(jī)進(jìn)行,這無疑在這段時間內(nèi)這段內(nèi)存會被一直占用。
- 垃圾回收對性能的消耗比較大,因?yàn)榫幾g器/解釋器需要不停的追蹤每個對象的引用計數(shù)來決定某個變量是否會被回收。
手動管理內(nèi)存
C/C++就采取了手動管理內(nèi)存的方式,也就是自己申請內(nèi)存,自己需要去釋放,如果不釋放就可能導(dǎo)致內(nèi)存占用的堆積,這種方式的好處在于可控性和高性能,壞處在于很容易出現(xiàn)各種錯誤。例如二次釋放。
void test(int arr[]){
free(arr); //此時該數(shù)組已經(jīng)被釋放
}
int main(){
int *arr = (int *)malloc(sizeof(int) * 10);
test(arr);
free(arr); //這里又進(jìn)行了第二次釋放
return 0;
}
上述代碼可能你覺著不會寫的這么蠢,但是如果當(dāng)代碼邏輯復(fù)雜起來,你很難保證你不會犯這樣的錯誤。
二次釋放的危害很大,因?yàn)楫?dāng)一塊內(nèi)存被釋放時可能會被新的對象再次占用,而地二次釋放則會破壞新的對象的存儲從而造成一系列嚴(yán)重的錯誤。
Rust的所有權(quán)
Rust采取了一種非傳統(tǒng)的做法,即采用了一種叫做所有權(quán)的機(jī)制,其規(guī)則如下:
- Rust 中每一個值都被一個變量所擁有,該變量被稱為值的所有者
- 一個值同時只能被一個變量所擁有,或者說一個值只能擁有一個所有者
- 當(dāng)所有者(變量)離開作用域范圍時,這個值將被丟棄(drop)
來看下面的例子
fn main() {
let x = 4; //x的作用域開始位置
{//y的作用域開始位置
let y = 10;
}//y的作用域結(jié)束位置,作用域結(jié)束,y被丟棄
}//x的作用域結(jié)束位置,作用域結(jié)束,x被丟棄
此時就需要提一下堆和棧這兩種內(nèi)存了,其主要的區(qū)別如下:
棧是一種后進(jìn)先出的內(nèi)存,其內(nèi)部高度組織化,并且棧內(nèi)存的數(shù)據(jù)需要實(shí)現(xiàn)知道其大小,堆棧內(nèi)存的存取速度很快
與棧內(nèi)存不同,堆可以存儲結(jié)果(指不能在編譯時就確定大小)未知大小的數(shù)據(jù),在存取時速度比較慢,OS會尋找一塊可以存的下的內(nèi)存然后分配給對應(yīng)的進(jìn)程。對于這塊堆內(nèi)存來說,會有一個指針指向這塊,而這個指針是要存在棧中的。
堆 | 棧 | |
---|---|---|
存取速度 | 慢 | 快 |
分配內(nèi)存大小 | 可不固定 | 必須固定 |
在Rust中,有以下類型都是存儲在棧內(nèi)存上的,這些類型都實(shí)現(xiàn)了一個叫做copy的trait
(圖片來自于b站楊旭)
由于這些類型都是存在棧上的,而棧的存儲速度遠(yuǎn)高于堆,所以對于Rust來說,上述的數(shù)據(jù)在賦值傳遞時往往會直接進(jìn)行拷貝,而不需要引用之類的技術(shù)。
fn main(){
let x = 10;
let y = x; //此時y有拷貝了一份10
}
所有權(quán)轉(zhuǎn)移
由于我們知道上述的類型都是存在棧的,所以體現(xiàn)不出所有權(quán),我們使用另一種存在堆上的類型String來演示所有權(quán)。
fn main(){
let s = String::from("hello"); //從字符串字面值得到一個String類型
}
這里需要區(qū)分一下String和字符串字面值的區(qū)別,這里的String是一個存在堆內(nèi)存的變量,其大小是可以擴(kuò)充變化的。而字符串字面值(如let x = “hello”;)是固定的,在編譯時就已經(jīng)確定的了。
來看下面的代碼:
fn main(){
let s1 = String::from("hello"); //從字符串字面值得到一個String類型
let s2 = s1;
println!("{}", s); //會報錯
}
上述代碼報錯了,我們來看報錯信息:
error[E0382]: borrow of moved value: `s1`
--> src\main.rs:4:17
|
2 | let s1 = String::from("hello"); //從字符串字面值得到一個String類型
| - move occurs because `s` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| - value moved here
4 | println!("{}", s1); //會報錯
| ^ value borrowed here after move
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
報錯的內(nèi)容說,我們借用了一個已經(jīng)移動了的值s。
還記著開頭所說的所有權(quán)規(guī)則么:
- Rust 中每一個值都被一個變量所擁有,該變量被稱為值的所有者
- 一個值同時只能被一個變量所擁有,或者說一個值只能擁有一個所有者
- 當(dāng)所有者(變量)離開作用域范圍時,這個值將被丟棄(drop)
String::from(“hello”);這個值最開始被s所擁有,但是當(dāng)讓s2 = s1時,這個值就不在歸s所有了,而是發(fā)生了move(移動),所有權(quán)交給了s2。
這是因?yàn)橛械诙l: 一個值只能同時被一個變量所擁有的緣故,所以s1不再擁有String::from(“hello”);,自然也就無法再被使用了。
我們從內(nèi)存上來看是這樣的(圖片來自于b站楊旭),剛開始s1是一個指針指向了hello這塊堆內(nèi)存
后來由于s2=s1,所以指針得到了復(fù)制,得到了如下的場景
但由于一個值只能有一個所有者,所以先前的s1指針將被視為無效,于是得到了如下的場景。
所以最終看起來就好像s1的所有權(quán)移動到了s2一樣,發(fā)生了所有權(quán)(對hello這個值的擁有)的移動。
這么做有一個很大的好處,那就是由于一個值在同一時刻只有一個所有者,那么在離開作用域時,對應(yīng)的值就不會被反復(fù)釋放
fn main(){
let s1 = String::from("hello"); //s1作用域開始
let s2 = s1;//s2作用域開始,s1失去所有權(quán)
}//s1,s2作用域結(jié)束,由于s1沒有所有權(quán),所以只有s2會釋放這塊堆內(nèi)存。
這樣就有效的避免了二次釋放的的問題,但是同時又給開發(fā)者引入了很多新的難題。
函數(shù)所有權(quán)傳遞
但一個變量被當(dāng)做參數(shù)傳遞給函數(shù)時,其所有權(quán)將會被移交到函數(shù)內(nèi)部的形式參數(shù)上??慈缦吕樱?/p>
fn get_len(s: String) -> usize{ //此時,這里的形式參數(shù)s得到了外部s1的所有權(quán)
return s.len(); //返回s的長度
} //s的作用域結(jié)束,而s又具有hello的所有權(quán),所以hello會被釋放
fn main(){
let s1 = String::from("hello"); //從字符串字面值得到一個String類型
let len = get_len(s1); //把s1交給函數(shù)get_len
println!("{}", s1); //由于此時所有權(quán)已經(jīng)交給了函數(shù)內(nèi)部的形式參數(shù)s,所以s1沒有所有權(quán),導(dǎo)致報錯
}
上面的注釋已經(jīng)說名了具體狀況,其發(fā)生錯誤的原因就在于傳遞參數(shù)時會發(fā)生所有權(quán)的轉(zhuǎn)移,而轉(zhuǎn)移后有沒有發(fā)生歸還,而導(dǎo)致使用了沒有所有權(quán)的變量。
解決方法1:
使用shadowing這個特性,將使用權(quán)再返回回去。
fn get_len(s: String) -> (usize, String){ //返回長度和字符串
return (s.len(), s);
}
fn main(){
let s = String::from("hello"); //從字符串字面值得到一個String類型
let (s, len) = get_len(s); //用Tuple拆包和shadowing的特性歸還使用權(quán)
println!("{}'s len is {}", s, len);
}
解決方法2:
參看下面的引用部分
如果你不想這么麻煩,你想直接復(fù)制一份hello傳過去,而不是吧使用權(quán)交過去,你可以使用clone方法,這個方法會把堆上的數(shù)據(jù)克隆一份,然后給傳給函數(shù)。
fn get_len(s: String) -> usize{ //返回長度和字符串
return s.len();
}
fn main(){
let s = String::from("hello"); //從字符串字面值得到一個String類型
let len = get_len(s.clone()); //把s克隆一份,此時新克隆的部分的使用權(quán)將交給形式參數(shù)s
println!("{}'s len is {}", s, len);
}
注意,克隆意味著你要在堆上復(fù)制一份數(shù)據(jù),這是相當(dāng)耗時的。但好處在于,你可以不用為所有權(quán)而煩惱。
引用與借用
每次想要傳一個值都會進(jìn)行所有權(quán)轉(zhuǎn)移顯然十分的頭疼,于是就有了借用,借用實(shí)際上就是一種引用,但是它的特殊之處在于他不會拿到所有權(quán)。 借用使用&操作符來表示。例子如下:
fn get_len(s: &String) -> usize{ //s是一個借用,而不是轉(zhuǎn)交所有權(quán)
return s.len();
}
fn main(){
let s1 = String::from("hello"); //從字符串字面值得到一個String類型
let len = get_len(&s1); //傳入s1的引用
println!("{}'s len is {}", s1, len);
}
借用在內(nèi)存上看是這個樣子的:
可以看到引用s只是指向了s1,并不是指向了堆內(nèi)存所在的位置。所以借用可以看做是變量的引用。因?yàn)橛羞@個特性,所以借用并沒有拿到了s1的所有權(quán),只是暫時的借了過來。
所以在get_len函數(shù)結(jié)束之后,由于s只是借用所以無權(quán)釋放hello。所有權(quán)還在s1手中。
可變與不可變引用
上述的借用很好的解決了傳參的問題,但是還沒完。有時我們希望修改一下對應(yīng)的值,如果你直接修改就會發(fā)現(xiàn)報錯這是因?yàn)槟銢]加mut關(guān)鍵字。
fn get_len(s: &mut String) -> usize{ //可變字符串引用
return s.len();
}
fn main(){
let mut s = String::from("hello"); //從字符串字面值得到一個String類型
let len = get_len(&mut s);//傳入可變字符串引用
println!("{}'s len is {}", s, len);
}
問題似乎解決了,似乎皆大歡喜,但是,更大的問題隨之出現(xiàn)了。我們來看下面的例子
fn main(){
let mut s: String = String::from("hello");
let mut re1: &String = &mut s;
let mut re2: &String = &mut s;//此處報錯
println!("{} {}", re1, re2);
}
報錯的理由很簡單,同一作用域內(nèi)不允許出現(xiàn)兩個同一變量的可變引用。Rust這么做是為了防止數(shù)據(jù)競爭的發(fā)生。數(shù)據(jù)競爭會由一下行為引發(fā):
1. 兩個或更多的指針同時訪問同一數(shù)據(jù)
2. 至少有一個指針被用來寫入數(shù)據(jù)
3. 沒有同步數(shù)據(jù)訪問的機(jī)制
(參考來源: https://course.rs/)
同時為了保證不可變引用不會因?yàn)榭勺円玫男薷亩l(fā)生異常,于是規(guī)定,在同一作用域內(nèi)不可變引用和可變引用不能同時出現(xiàn)。
fn main(){
let mut s: String = String::from("hello");
let mut re1: &String = &s;
let mut re2: &String = &mut s; //不能同時出現(xiàn)
println!("{} {}", re1, re2);
}
但是在同一作用域內(nèi),可以同時出現(xiàn)多個不可變引用。
如果實(shí)在需要用到多個可變引用,則可以通過大括號來創(chuàng)建新的作用域。
fn main(){
let mut s: String = String::from("hello");
{
let mut re1: &String = &s;
}
let mut re2: &String = &mut s;
println!("{}", re2); //此時已經(jīng)無法調(diào)用re1,因?yàn)樗淖饔糜蛟诖罄ㄌ杻?nèi),在此處已經(jīng)失效
}
生命周期
對于Rust來說每個變量都有一個自己的生命周期,也就是說,每個變量有一個有效地范圍。
如果一個變量已經(jīng)失效,而仍然使用他的引用,就會造成懸垂引用。而Rust的生命周期就是為了避免懸垂引用這個錯誤而設(shè)計的。
懸垂引用
來看下面的代碼
fn main() {
let result;
{
let tmp = String::from("abc");
result = &tmp;
}
println!("{}", result);
}
上述代碼會報錯,這是因?yàn)閞esult的生命周期在整個main內(nèi),但是tmp只是內(nèi)部的一個局部變量,在print時,tmp已經(jīng)失效可以認(rèn)為是空的。
但是此時result仍然持有并想使用tmp的引用,因此引發(fā)了報錯。來看一下報錯內(nèi)容:
error[E0597]: `tmp` does not live long enough
--> src\main.rs:6:18
|
5 | let tmp = String::from("abc");
| --- binding `tmp` declared here
6 | result = &tmp;
| ^^^^ borrowed value does not live long enough
7 | }
| - `tmp` dropped here while still borrowed
8 | println!("{}", result);
| ------ borrow later used here
For more information about this error, try `rustc --explain E0597`.
內(nèi)容說的很直白,說的是result借用了一個獲得還沒自己長的變量。
我們來看看兩個變量的生命周期(就是存活時間)
fn main() {
let result;-------------------------------+
{ |<-result的生命周期
let tmp = String::from("abc");-----+ |
result = &tmp; tmp的生命周期-> | |
}--------------------------------------+ |
println!("{}", result); |
}---------------------------------------------+
從上圖可以直觀的看到,result的存活范圍(生命周期)大于tmp的生命周期。一個大的生命周期的變量借用了一個小的,所以才會導(dǎo)致了錯誤的發(fā)生。
我們只需要修改上述代碼就可以使其正確
fn main() {
let result;
let tmp = String::from("abc");
result = &tmp;
println!("{}", result);
}
此時在調(diào)用print時,tmp的生命周期還沒有結(jié)束,但是此時你也能發(fā)現(xiàn)result的生命周期其實(shí)還是大于tmp的,也就意味著
大周期借用小周期不一定會出錯。但是在很多情況下,Rust還是認(rèn)為這種情況存在風(fēng)險,會在編譯階段就拒絕我們。
但是小周期借用大周期確實(shí)一定不會出錯的(因?yàn)榇藭r小周期會率先失效,而不會借用到一個失效的大周期對象)
函數(shù)生命周期聲明
先來看一個函數(shù),有一個需求,要求返回兩個字符串中最長的那一個的借用。此時你會想這么寫代碼
fn get_greater(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
此時你覺著則個代碼寫得沒問題,但是你卻發(fā)現(xiàn)編譯器報錯了。你會發(fā)現(xiàn)錯誤如下:
error[E0106]: missing lifetime specifier
--> src\main.rs:9:37
|
9 | fn get_greater(x: &str, y: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
什么意思呢,也就是說Rust期望你手動添加一個生命周期的聲明。
在Rust眼里,這個代碼會返回一個字符串的引用&str。而這個引用可能來自x或者y,或者是其他的地方。
這取決于你如何寫函數(shù)內(nèi)部的實(shí)現(xiàn),此時的Rust編譯器犯迷糊了因?yàn)樗恢?amp;str的生命周期大概是多長。
是和x一樣長?還是和y一樣長?還是什么?如果是和x一樣長,且x的生命周期大于y的,那么返回一個y的引用就可能會出現(xiàn)懸空指針。
看下面例子:
fn main() {
let result;
let s1 = String::from("abc"); //s1生命周期大
{
let s2 = String::from("abcd");//s2生命周期小
result = get_greater(s1.as_str(), s2.as_str()); //把小的引用賦給了大的
}
println!("{}", result); //此時s2已經(jīng)銷毀,result指針懸空
}
fn get_greater(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y //此時返回了小的那一個
}
}
上面的代碼就產(chǎn)生了這種不安全的行為,歸其原因在于我們可能會返回一個小的生命周期的借用給一個大的生命周期的變量。
而此時由于Rust并不清楚你的函數(shù)干了什么操作,所以推斷不出返回值的借用的生命周期到底是大的還是小的。
所以此時Rust要求你對傳入的參數(shù)的引用的生命周期加以限定,以保證返回值的生命周期是可以被Rust編譯器推斷的(沒錯,生命周期標(biāo)注就是為了告訴Rust編譯器你的返回值的生命周期是多大,從而讓Rust能夠檢查出潛在的懸空指針錯誤)
生命周期標(biāo)注符號使用’作為開頭,例如
'a
'b
'abc
他么需要放在函數(shù)名后的尖括號里,表明這是一個生命周期標(biāo)注變量
fn get_greater<'a>(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
然后你就需要為后續(xù)的引用標(biāo)注上生命周期。其寫法是在&后面加上生命周期標(biāo)注變量再加類型
fn get_greater<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
我們來看上面的代碼,定義了一個生命周期標(biāo)注變量’a, 其中x生命周期的大小是’a, y也是。返回值也是。也就是說x,y,返回值有同樣的生命周期。
你可能會疑惑這是啥意思,x和y顯然可能不同。此時你可以認(rèn)為‘a(chǎn)的大小就是x和y中最小的那一個(顯然讓一個小的強(qiáng)行擁有大的是錯誤的)。
所以上述代碼的標(biāo)注在告訴編譯器兩件事:
- 對函數(shù)內(nèi)部,返回的引用必須要和x和y的生命周期相同,否則函數(shù)內(nèi)部的實(shí)現(xiàn)有問題。
- 對于函數(shù)外部,返回的值是x和y生命周期中最小的那一個,所以在外部進(jìn)行檢查時,如果將這個小的賦給了大的,那么編譯器可以直接預(yù)判報錯。
看完上述的兩點(diǎn),你會發(fā)現(xiàn)加了生命周期并不改變程序的一分一毫,只是給編譯器指明了一條檢查你錯誤的道路。
我們再來看下面代碼加深對這句話的理解:
fn main() {
let result;
let s1 = String::from("abc");
{
let s2 = String::from("abcd");
result = get_greater(s1.as_str(), s2.as_str());
}
println!("{}", result);
}
fn get_greater<'a>(x: &'a str, y: &str) -> &'a str {
if x.len() > y.len() {
x
} else {
y //此處會報錯
}
}
上述代碼只給x和返回值加上了生命周期的限制,也就是說返回值和x有一樣的生命周期。
但是此時Rust編譯器檢查你的代碼,發(fā)現(xiàn)了你邏輯上的漏洞,因?yàn)槟惴祷亓藋,而y的生命周期并不是’a。所以此時在y處報錯
fn main() {
let result;
let s1 = String::from("abc");
{
let s2 = String::from("abcd");
result = get_greater(s1.as_str(), s2.as_str()); //在此處報錯
}
println!("{}", result);
}
fn get_greater<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
此時我們對函數(shù)加以修正,Rust編譯器會發(fā)現(xiàn),此時函數(shù)內(nèi)部返回值都有‘a(chǎn)的生命周期。所以函數(shù)內(nèi)部的邏輯是正確的。
那么此時Rust編譯器就會開始對外對調(diào)用者進(jìn)行檢查了,由于函數(shù)指明了x和y有相同的生命周期。
但是其中一個生命周期過?。╯2的過?。?,導(dǎo)致’a就是過小的這個生命周期('a就是s2)的生命周期。此時編譯器檢查了一遍后就發(fā)現(xiàn)(編譯器只看函數(shù)名就知道返回值的生命周期了,不需要知道函數(shù)內(nèi)部的實(shí)現(xiàn)細(xì)節(jié),這就是為啥要標(biāo)注生命周期,就是給編譯器看的),返回值的生命周期小于它要賦給的變量的生命周期。
也就是開頭的大周期變量借用小周期變量。所以此時果斷報錯。
綜上可以看出一件事,生命周期的標(biāo)注就是為了讓Rust能夠在編譯時,對函數(shù)內(nèi)檢查你的邏輯上的錯誤,對外檢查調(diào)用時是否會發(fā)生錯誤。所以這個標(biāo)注只起到檢查作用(伺候好編譯器老爺 )
結(jié)構(gòu)體的生命周期聲明
在結(jié)構(gòu)體和枚舉中也有可能出現(xiàn)引用,此時我們就需要給每個引用標(biāo)注一下生命周期了。
如下:
struct Test<'a>{
name: &'a String;
}
此時這個標(biāo)注的含義就是,name這個生命周期至少要比結(jié)構(gòu)體活得還要長。我們可以舉一個反例,如下:
struct Test<'a>{
name: &'a String
}
fn main() {
let test;
{
let name = String::from("abc");
test = Test{
name: &name //此處會報錯,說name生命周期太短
};
}
println!("{}",test.name);
}
此時Rust檢查后發(fā)現(xiàn)了,name還沒test活得長,所以果斷報錯。
主義在實(shí)現(xiàn)時實(shí)使用impl實(shí)現(xiàn)的時候也需要標(biāo)注上生命周期,因?yàn)檫@個相當(dāng)于是結(jié)構(gòu)體名的一部分。
impl<'a> Test<'a>{
fn print_hello(&self) -> (){
}
}
Rust生命周期的自行推斷
在某些情況下Rust可以自己推斷出返回值的生命周期,主要按照如下的規(guī)則:
我們來看例子1:
fn test(s: &String) -> &String{
}
上面的代碼就沒有報錯,這是因?yàn)镽ust已經(jīng)推斷出了返回值的生命周期。
- 根據(jù)第一條規(guī)則,每個引用類型的參數(shù)都有自己的生命周期,于是s有一個生命周期
- 根據(jù)第二條規(guī)則,只有一個輸入,于是這個輸入的生命周期將會給于返回值
于是Rust推斷出了返回值引用的生命周期,這是因?yàn)榉祷刂档纳芷谥豢赡軄碜杂谳斎?,因?yàn)楹瘮?shù)內(nèi)部的創(chuàng)建的對象,在返回引用時會造成懸垂引用(因?yàn)楹瘮?shù)一結(jié)束,被引用的對象就失效了)。
再來看另一種情況
impl<'a> Test<'a>{
fn print_hello(&self, word: &String) -> &String{
word
}
}
上述情況也不會報錯,理由如下:
- 根據(jù)第一條規(guī)則,每個引用類型的參數(shù)都有自己的生命周期,于是self和word有自己的生命周期
- 根據(jù)第三條原則,self的生命周期會被賦給word,和返回值,此時所有的參數(shù)的生命周期都已經(jīng)推斷出。
生命周期約束
對于生命周期標(biāo)注來說,我們可以標(biāo)注任意多的類別例如下面的這個式子:
fn test<'a, 'b>(s1: &'a String, s2: &'b String) -> &String{
s1
}
當(dāng)然,上述的代碼是錯誤的,因?yàn)榫幾g器無法推斷出返回值的生命周期。
我們稍作修改后就得到了這個:
fn test<'a, 'b>(s1: &'a String, s2: &'b String) -> &'a String{
s1
}
此時我們就得到了這個式子,就不報錯了,如果我們突發(fā)奇想換一個式子呢?我們返回s2會如何
fn test<'a, 'b>(s1: &'a String, s2: &'b String) -> &'a String{
s2
}
此時編譯器又報錯了,因?yàn)榫幾g器不知道s1和s2到底啥關(guān)系,返回的應(yīng)該是’a的周期,但是實(shí)際返回的是’b的周期。
此時我們可以通過對生命周期的關(guān)系增加約束來達(dá)到解決問題的目的:
fn test<'a, 'b>(s1: &'a String, s2: &'b String) -> &'a String
where 'b: 'a //'b生命周期大于'a
{
s2
}
此時又不報錯了,因?yàn)榇藭r我們返回的是’b周期,而他比’a是要更大的。所以這個返回不會造成懸垂引用(因?yàn)榉祷氐谋取痑要大,'a會造成的懸垂引用,'b不一定會造成)
靜態(tài)生命周期
對于一些變量它的生命周期可能是整個程序的運(yùn)行期間,于是可以使用’static這個特殊的標(biāo)注來聲明一個整個程序期間的生命周期。文章來源:http://www.zghlxwxcb.cn/news/detail-651340.html
fn test(s: &'static str){
}
其中,常見的字符串字面值&str采用的就是’static類型的生命周期文章來源地址http://www.zghlxwxcb.cn/news/detail-651340.html
到了這里,關(guān)于Rust語法:所有權(quán)&引用&生命周期的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!