欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入了解Rust中引用與借用的用法

 更新時(shí)間:2022年11月03日 12:28:27   作者:古明地覺  
這篇文章主要為大家詳細(xì)介紹了Rust語言中引用與借用的使用,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的小伙伴可以了解一下

楔子

好久沒更新 Rust 了,上一篇文章中我們介紹了 Rust 的所有權(quán),并且最后定義了一個(gè) get_length 函數(shù),但調(diào)用時(shí)會(huì)導(dǎo)致 String 移動(dòng)到函數(shù)體內(nèi)部,而我們又希望在調(diào)用完畢后能繼續(xù)使用該 String,所以不得不使用元組將 String 也作為元素一塊返回。

//?該函數(shù)計(jì)算一個(gè)字符串的長度
fn?get_length(s:?String)?->?(String,?usize)?{
????//?因?yàn)檫@里的?s?會(huì)獲取變量的所有權(quán)
????//?而一旦獲取,那么調(diào)用方就不能再使用了
????//?所以我們除了要返回計(jì)算的長度之外
????//?還要返回這個(gè)字符串本身,也就是將所有權(quán)再交回去
????let?length?=?s.len();
????(s,?length)
}


fn?main()?{
????let?s?=?String::from("古明地覺");

????//?接收長度的同時(shí),還要接收字符串本身
????//?將所有權(quán)重新?"奪"?回來
????let?(s,?length)?=?get_length(s);
????println!("s?=?{},?length?=?{}",?s,?length);?
????/*
????s?=?古明地覺,?length?=?12
????*/
}

但這種寫法很笨拙,下面我們將 get_length 函數(shù)重新定義,并學(xué)習(xí) Rust 的引用。

什么是引用

新的函數(shù)簽名使用了 String 的引用作為參數(shù),而沒有直接轉(zhuǎn)移所有權(quán)。

fn?get_length(s:?&String)?->?usize?{
????s.len()
}

fn?main()?{
????let?s1?=?String::from("hello");
????let?length?=?get_length(&s1);
????println!("s1?=?{},?length?=?{}",?s1,?length);?
????//?s1?=?hello,?length?=?5
}

首先需要注意的是,變量聲明以及函數(shù)返回值中的那些元組代碼都消失了。其次在調(diào)用 get_length 函數(shù)時(shí)使用了 &s1 作為參數(shù),并且在函數(shù)的定義中,我們使用 &String 替代了 String。而 & 代表的就是引用語義,它允許我們?cè)诓猾@取所有權(quán)的前提下使用值。

既然有引用,那么自然就有解引用,它使用 * 作為運(yùn)算符,含義和引用相反,我們會(huì)在后續(xù)詳細(xì)地介紹。

現(xiàn)在,讓我們仔細(xì)觀察一下這個(gè)函數(shù)的調(diào)用過程:

let?s1?=?String::from("hello");
let?length?=?get_length(&s1);

這里的 &s1 允許我們?cè)诓晦D(zhuǎn)移所有權(quán)的前提下,創(chuàng)建一個(gè)指向 s1 值的引用,由于引用不持有值的所有權(quán),所以當(dāng)引用離開當(dāng)前作用域時(shí),它指向的值也不會(huì)被丟棄。同理,函數(shù)簽名中的 & 用來表明參數(shù) s 的類型是一個(gè)引用。

???????????//?s?是一個(gè)指向?String?的引用
fn?get_length(s:?&String)?->?usize?{?
????s.len()
}??//?到這里?s?離開作用域
???//?但由于它并不持有自己指向值的所有權(quán)
???//?所以最終不會(huì)發(fā)生任何事情

此處變量 s 的有效作用域與其它任何函數(shù)參數(shù)一樣,但唯一不同的是,它不會(huì)在離開自己的作用域時(shí)銷毀其指向的數(shù)據(jù),因?yàn)樗⒉粨碛性摂?shù)據(jù)的所有權(quán)。當(dāng)一個(gè)函數(shù)使用引用而不是值本身作為參數(shù)時(shí),我們便不需要為了歸還所有權(quán)而特意去將值返回,畢竟在這種情況下,我們根本沒有取得所有權(quán)。

而將引用傳遞給函數(shù)參數(shù)的這一過程被稱為借用(borrowing),在現(xiàn)實(shí)生活中,假如一個(gè)人擁有某件東西,你可以從他那里把東西借過來。但是當(dāng)你使用完畢時(shí),還必須將東西還回去。

Rust 的變量也是如此,如果一個(gè)值屬于該變量,那么該變量離開作用域時(shí)會(huì)銷毀對(duì)應(yīng)的值,就好比東西你不想要了,你可以將它扔掉,因?yàn)闁|西是你的。但如果是借用的話,變量在離開作用域時(shí),這個(gè)值并不會(huì)被銷毀,就好比東西你不想要了,但這個(gè)東西并不屬于你,因此你要將它還回去,并且這個(gè)東西還在。

至于后續(xù)這個(gè)東西是否會(huì)被扔掉、何時(shí)被扔掉,就看它真正的主人是否還需要它,如果不需要了,東西的主人是有權(quán)利銷毀的,因?yàn)檫@東西是他的。當(dāng)然,他也可以將東西送給別人,此時(shí)就相當(dāng)于發(fā)生了所有權(quán)的轉(zhuǎn)移,轉(zhuǎn)移之后這東西跟他也沒關(guān)系了。

然后問題來了,如果我們嘗試修改借用的值會(huì)怎么樣呢?相信你能猜到,肯定是不允許的,還是拿借東西舉例子,東西既然是借的,就說明你只有使用權(quán),而沒有修改它的權(quán)利。

fn?change_string(s:?&String)?{
????s.push_str("?world");
}

fn?main()?{
????let?s1?=?String::from("hello");
????change_string(&s1);
}

執(zhí)行這段代碼會(huì)出現(xiàn)編譯錯(cuò)誤:

與變量類似,引用是默認(rèn)不可變的,Rust 不允許我們?nèi)バ薷囊弥赶虻闹怠?/p>

可變引用

我們可以通過一個(gè)小小的調(diào)整來修復(fù)上面的示例中出現(xiàn)的編譯錯(cuò)誤:

fn?change_string(s:?&mut?String)?{
????s.push_str("?world");
}

fn?main()?{
????let?mut?s1?=?String::from("hello");
????change_string(&mut?s1);
}

首先我們需要將變量 s1 聲明為 mut,即可變的,也就是東西的主人能夠允許它的東西發(fā)生變化。其次,要使用 &mut s1 來給函數(shù)傳入一個(gè)可變引用,意思就是東西的主人在將東西借給別人時(shí)專門強(qiáng)調(diào)了,自己的東西允許修改,不然別人不知道啊。

所以這里如果不傳遞可變引用的話,即使 s1 是可變的,函數(shù) change_string 里面也不能對(duì)值進(jìn)行修改。因此調(diào)用函數(shù)的時(shí)候要傳遞可變引用,當(dāng)然函數(shù)參數(shù)接收的也要是一個(gè)可變引用,因?yàn)轭愋鸵ヅ洹?/p>

另外,除了將引用作為參數(shù)傳遞之外,還可以賦值給一個(gè)變量,因?yàn)樽鳛楹瘮?shù)參數(shù)和賦值給一個(gè)變量是等價(jià)的。

fn?main()?{
????let?mut?s1?=?String::from("hello");
????//?可變引用指的是,引用指向的值可以修改
????//?所以要注意這里的寫法,不要寫成了?let?mut?s2:?&String
????//?這表示?s2?是個(gè)不可變引用,但?s2?本身是可變的
????//?可變引用是一個(gè)整體,所以?&mut?String?要整體作為?s2?的類型
????let?s2:?&mut?String?=?&mut?s1;
????//?當(dāng)然啦,此時(shí)?s2?引用的值可變,但?s2?本身不可變
????//?如果希望?s2?還能接收其它字符串的可變引用,那么應(yīng)該這么聲明
????//?let?mut?s2:?&mut?String?=?&mut?s1;
????//?此時(shí)表示?s2?是個(gè)可變引用,它引用的值可以修改
????//?并且 s2 本身也是可變的?;蛘哌€有更簡單的寫法:
????//?直接寫成?let?mut s2?=?&mut?s1?也行,因?yàn)?Rust?會(huì)做類型推斷
???
????s2.push_str("?world");
????println!("{}",?s1);??//?hello?world
}

此外要注意:當(dāng)變量聲明為不可變時(shí),只能創(chuàng)建不可變引用。

fn?main()?{
????let?s1?=?String::from("hello");
????let?s2:?&mut?String?=?&mut?s1;
????println!("{}",?s2);?
}

代碼中的 s1 不可變,但卻創(chuàng)建了可變引用,于是報(bào)錯(cuò)。

因?yàn)?s1 是不可變的,就意味著數(shù)據(jù)(包括棧內(nèi)存、堆內(nèi)存)不可以修改,所以此時(shí)不能創(chuàng)建可變引用,否則就意味著值是可以修改的,于是就矛盾了。因此當(dāng)變量聲明為不可變時(shí),不可以將可變引用賦值給其它變量。

但當(dāng)變量聲明為可變時(shí),既可以創(chuàng)建可變引用,也可以創(chuàng)建不可變引用。如果是可變引用,那么允許通過引用修改值;如果是不可變引用,那么不允許通過引用修改值。

fn?main()?{
????//?變量可變
????let?mut?s1?=?String::from("hello");
????//?可以通過?&s1?創(chuàng)建不可變引用
????//?也可以通過?&mut?s1?創(chuàng)建可變引用
????//?但前者不可以修改值,后者可以
}

另外可變引用有一個(gè)很大的限制:對(duì)于特定作用域中的特定數(shù)據(jù)來說,一次只能聲明一個(gè)可變引用,否則會(huì)導(dǎo)致編譯錯(cuò)誤。

fn?main()?{
????let?mut?s1?=?String::from("hello");
????let?s2?=?&mut?s1;
????let?s3?=?&mut?s1;
????s2.push_str("xx");
????s3.push_str("yy");
????println!("{}",?s1);
}

我們將 s1 的可變引用給了 s2 之后又給了 s3,而這是非法的。

但 Rust 做了一個(gè) "容忍" 操作,那就是聲明多個(gè)引用之后,如果都不使用的話,那么也不會(huì)出現(xiàn)錯(cuò)誤。

fn?main()?{
????let?mut?s1?=?String::from("hello");
????let?s2?=?&mut?s1;
????let?s3?=?&mut?s1;
????println!("{}",?s1);??//?hello
}

以上這段代碼可以順利執(zhí)行,雖然聲明了多個(gè)可變引用,但我們沒有使用,所以 Rust 編譯器就大發(fā)慈悲 "饒" 了我們。但只要對(duì)任意某個(gè)引用執(zhí)行了任意某個(gè)操作,那么 Rust 就不會(huì)再手下留情了,比如:

fn?main()?{
????let?mut?s1?=?String::from("hello");
????let?s2?=?&mut?s1;
????let?s3?=?&mut?s1;
????println!("{}",?s2);?
}

我們上面對(duì) s2 執(zhí)行了打印操作,于是 Rust 就會(huì)提示我們可變引用只能被借用一次。

但說實(shí)話 Rust 編譯器做的這個(gè) "忍讓" 對(duì)于我們而言沒有太大意義,因?yàn)樗笪覀兟暶鞫鄠€(gè)可變引用之后不能使用其中的任何一個(gè),但問題是聲明引用就是為了使用它,不然聲明它干嘛。因此我們?nèi)钥梢哉J(rèn)為:對(duì)于特定作用域中的特定數(shù)據(jù)來說,一次只能聲明一個(gè)可變引用,否則會(huì)導(dǎo)致編譯錯(cuò)誤。

這個(gè)規(guī)則使得引用的可變性只能以一種受到嚴(yán)格限制的方式來使用,許多剛剛接觸 Rust 的開發(fā)者會(huì)反復(fù)地與它進(jìn)行斗爭,因?yàn)榇蟛糠值恼Z言都允許你隨意修改變量。但另一方面,在 Rust 中遵循這條限制性規(guī)則可以幫助我們?cè)诰幾g時(shí)避免數(shù)據(jù)競爭。數(shù)據(jù)競爭(data race)與競態(tài)條件十分類似,它會(huì)在指令同時(shí)滿足以下 3 種情形時(shí)發(fā)生:

  • 兩個(gè)或兩個(gè)以上的指針同時(shí)訪問同一空間;
  • 其中至少有一個(gè)指針會(huì)向空間中寫入數(shù)據(jù);
  • 沒有同步數(shù)據(jù)訪問的機(jī)制;

數(shù)據(jù)競爭會(huì)導(dǎo)致未定義的行為,由于這些未定義的行為往往難以在運(yùn)行時(shí)進(jìn)行跟蹤,也就使得出現(xiàn)的 bug 更加難以被診斷和修復(fù)。Rust 則完美地避免了這種情形的出現(xiàn),因?yàn)榇嬖跀?shù)據(jù)競爭的代碼連編譯檢查都無法通過??。

與大部分語言類似,我們可以通過花括號(hào)來創(chuàng)建一個(gè)新的作用域范圍,這就使我們可以創(chuàng)建多個(gè)可變引用,當(dāng)然,同一時(shí)刻只允許有一個(gè)可變引用。

fn?main()?{
????let?mut?s1?=?String::from("hello");
????{
????????let?s2?=?&mut?s1;
????????s2.push_str("?cruel");
????????println!("s2?=?{}",?s2);
????????println!("s1?=?{}",?s1);
????}
????//?這個(gè)?s3?不能聲明在上面的大括號(hào)之前,也就是不能先聲明?s3
????//?因?yàn)橄嚷暶?s3?的話,那么聲明?s2?的時(shí)候就會(huì)出現(xiàn)兩個(gè)可變引用
????//?違反了同一時(shí)刻只能有一個(gè)可變引用的原則
????//?但是將?s3?聲明在這里就沒有問題,因?yàn)槁暶?s2?的時(shí)候?s3?還不存在
????//?聲明?s3?的時(shí)候?s2?已經(jīng)失效了
????//?所以此時(shí)滿足同一時(shí)刻只能有一個(gè)可變引用的原則,我生君未生、君生我已死
????let?s3?=?&mut?s1;
????s3.push_str("?world");
????println!("s3?=?{}",?s3);??
????println!("s1?=?{}",?s1);??
????/*
????s2?=?hello?cruel
????s1?=?hello?cruel
????s3?=?hello?cruel?world
????s1?=?hello?cruel?world
?????*/
}

注意:我們一直說的"一個(gè)可變引用"、"多個(gè)可變引用",它們針對(duì)的都是同一變量;如果是多個(gè)彼此無關(guān)的變量,那么它們的可變引用之間也沒有關(guān)系,此時(shí)是可以共存的。比如同一時(shí)刻有 N 個(gè)可變引用,但它們引用的都是不同的變量,所以此時(shí)沒有問題。

我們一直說的不允許存在多個(gè)可變引用,指的是同一變量的多個(gè)可變引用,這一點(diǎn)要分清楚。

如果是編程老手的話,那么應(yīng)該會(huì)想到,如果同時(shí)存在可變引用和不可變引用會(huì)發(fā)生什么呢?我們?cè)囈幌戮椭懒恕?/p>

fn?main()?{
????let?mut?s1?=?String::from("hello");
????let?s2?=?&s1;
????let?s3?=?&mut?s1;
????println!("{}",?s2);
????println!("{}",?s3)
}

所以在結(jié)合使用可變引用與不可變引用時(shí),還有一條類似的限制規(guī)則,我們不能在擁有不可變引用的同時(shí)創(chuàng)建可變引用,否則不可變引用就沒有意義了。但同時(shí)存在多個(gè)不可變引用是合理合法的,數(shù)據(jù)的讀操作之間不會(huì)彼此影響。

就有點(diǎn)類似于讀鎖和寫鎖的關(guān)系。

盡管這些編譯錯(cuò)誤會(huì)讓人不時(shí)地感到沮喪,但是請(qǐng)牢記一點(diǎn):Rust 編譯器可以為我們提早(在編譯時(shí)而不是運(yùn)行時(shí))暴露那些潛在的bug,并且明確指出出現(xiàn)問題的地方。你不再需要去追蹤調(diào)試為何數(shù)據(jù)會(huì)在運(yùn)行時(shí)發(fā)生了非預(yù)期的變化。

懸空引用

使用擁有指針概念的語言會(huì)非常容易錯(cuò)誤地創(chuàng)建出懸空指針,這類指針指向曾經(jīng)存在的某處內(nèi)存,但現(xiàn)在該內(nèi)存已經(jīng)被釋放掉、或者被重新分配另作他用了。而在 Rust 語言中,編譯器會(huì)確保引用永遠(yuǎn)不會(huì)進(jìn)入這種懸空狀態(tài),假如我們當(dāng)前持有某個(gè)數(shù)據(jù)的引用,那么編譯器可以保證這個(gè)數(shù)據(jù)不會(huì)在引用被銷毀前離開自己的作用域。

讓我們?cè)囍鴣韯?chuàng)建一個(gè)懸空引用,并看一看 Rust 是如何在編譯期發(fā)現(xiàn)這個(gè)錯(cuò)誤的:

fn?dangle()?->?&String?{
????let?s?=?String::from("hello?world");
????&s
}

fn?main()?{
????
}

出現(xiàn)的錯(cuò)誤如下所示:

這段錯(cuò)誤的提示信息包含了一個(gè)我們還沒有接觸的概念:生命周期,我們會(huì)后續(xù)詳細(xì)討論它。但即使不考慮生命周期,甚至不看錯(cuò)誤提示,我們也知道原因。dangle 里面的字符串 s 在函數(shù)結(jié)束后就會(huì)失效,內(nèi)存會(huì)回收,但我們卻返回了它的引用。

此處和 C 就出現(xiàn)了不同,C 中的堆內(nèi)存如果我們不手動(dòng)釋放,那么它是不會(huì)自己釋放的。而 Rust 中的堆內(nèi)存會(huì)在變量離開作用域的時(shí)候自動(dòng)回收,既然回收了,那么再返回它的引用就不對(duì)了,因?yàn)橹赶虻膬?nèi)存是無效的。所以我們也能猜到生命周期是做什么的,后續(xù)聊。

而這個(gè)問題的解決辦法也很簡單,直接返回 String 就好。

fn?dangle()?->?String?{
????let?s?=?String::from("hello?world");
????s
}

這種寫法沒有任何問題,因?yàn)樗袡?quán)從 dangle 函數(shù)中被轉(zhuǎn)移出去了,自然也就不會(huì)涉及釋放操作了。

小結(jié)

讓我們簡要地概括一下對(duì)引用的討論:

在任何一段給定的時(shí)間里,要么只能擁有一個(gè)可變引用,要么只能擁有任意數(shù)量的不可變引用;

引用總是有效的;

到此這篇關(guān)于深入了解Rust中引用與借用的用法的文章就介紹到這了,更多相關(guān)Rust引用 借用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • rust實(shí)現(xiàn)post小程序(完整代碼)

    rust實(shí)現(xiàn)post小程序(完整代碼)

    這篇文章主要介紹了rust實(shí)現(xiàn)一個(gè)post小程序,本文通過示例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-04-04
  • rust 包模塊組織結(jié)構(gòu)詳解

    rust 包模塊組織結(jié)構(gòu)詳解

    RUST提供了一系列的功能來幫助我們管理代碼,包括決定哪些細(xì)節(jié)是暴露的、哪些細(xì)節(jié)是私有的,以及不同的作用域的命名管理,這篇文章主要介紹了rust 包模塊組織結(jié)構(gòu)的相關(guān)知識(shí),需要的朋友可以參考下
    2023-12-12
  • rust閉包的使用

    rust閉包的使用

    閉包在Rust中是非常強(qiáng)大的功能,允許你編寫更靈活和表達(dá)性的代碼,本文主要介紹了rust閉包的使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12
  • Rust動(dòng)態(tài)數(shù)組Vec基本概念及用法

    Rust動(dòng)態(tài)數(shù)組Vec基本概念及用法

    Rust中的Vec是一種動(dòng)態(tài)數(shù)組,它可以在運(yùn)行時(shí)自動(dòng)調(diào)整大小,本文主要介紹了Rust動(dòng)態(tài)數(shù)組Vec基本概念及用法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12
  • rust類型轉(zhuǎn)換的實(shí)現(xiàn)

    rust類型轉(zhuǎn)換的實(shí)現(xiàn)

    Rust是類型安全的語言,因此在Rust中做類型轉(zhuǎn)換不是一件簡單的事,本文主要介紹了rust類型轉(zhuǎn)換的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12
  • Rust 函數(shù)詳解

    Rust 函數(shù)詳解

    函數(shù)在 Rust 語言中是普遍存在的。Rust 支持多種編程范式,但更偏向于函數(shù)式,函數(shù)在 Rust 中是“一等公民”,函數(shù)可以作為數(shù)據(jù)在程序中進(jìn)行傳遞,對(duì)Rust 函數(shù)相關(guān)知識(shí)感興趣的朋友一起看看吧
    2021-11-11
  • Rust指南枚舉類與模式匹配詳解

    Rust指南枚舉類與模式匹配詳解

    這篇文章主要介紹了Rust指南枚舉類與模式匹配精講,枚舉允許我們列舉所有可能的值來定義一個(gè)類型,枚舉中的值也叫變體,今天通過一個(gè)例子給大家詳細(xì)講解,需要的朋友可以參考下
    2022-09-09
  • Rust你不認(rèn)識(shí)的所有權(quán)

    Rust你不認(rèn)識(shí)的所有權(quán)

    所有權(quán)對(duì)大多數(shù)開發(fā)者而言是一個(gè)新穎的概念,它是 Rust 語言為高效使用內(nèi)存而設(shè)計(jì)的語法機(jī)制。所有權(quán)概念是為了讓 Rust 在編譯階段更有效地分析內(nèi)存資源的有用性以實(shí)現(xiàn)內(nèi)存管理而誕生的概念
    2023-01-01
  • Rust中的Cargo構(gòu)建、運(yùn)行、調(diào)試

    Rust中的Cargo構(gòu)建、運(yùn)行、調(diào)試

    Cargo是rustup安裝后自帶的,Cargo?是?Rust?的構(gòu)建系統(tǒng)和包管理器,這篇文章主要介紹了Rust之Cargo構(gòu)建、運(yùn)行、調(diào)試,需要的朋友可以參考下
    2022-09-09
  • 在Rust中編寫自定義Error的詳細(xì)代碼

    在Rust中編寫自定義Error的詳細(xì)代碼

    Result<T, E> 類型可以方便地用于錯(cuò)誤傳導(dǎo),Result<T, E>是模板類型,實(shí)例化后可以是各種類型,但 Rust 要求傳導(dǎo)的 Result 中的 E 是相同類型的,所以我們需要編寫自己的 Error 類型,本文給大家介紹了在Rust中編寫自定義Error的詳細(xì)代碼,需要的朋友可以參考下
    2024-01-01

最新評(píng)論