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

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

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

楔子

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

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


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

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

但這種寫法很笨拙,下面我們將 get_length 函數(shù)重新定義,并學習 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ù)時使用了 &s1 作為參數(shù),并且在函數(shù)的定義中,我們使用 &String 替代了 String。而 & 代表的就是引用語義,它允許我們在不獲取所有權(quán)的前提下使用值。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

可變引用

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

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ù)傳入一個可變引用,意思就是東西的主人在將東西借給別人時專門強調(diào)了,自己的東西允許修改,不然別人不知道啊。

所以這里如果不傳遞可變引用的話,即使 s1 是可變的,函數(shù) change_string 里面也不能對值進行修改。因此調(diào)用函數(shù)的時候要傳遞可變引用,當然函數(shù)參數(shù)接收的也要是一個可變引用,因為類型要匹配。

另外,除了將引用作為參數(shù)傳遞之外,還可以賦值給一個變量,因為作為函數(shù)參數(shù)和賦值給一個變量是等價的。

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

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

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

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

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

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

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

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

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 做了一個 "容忍" 操作,那就是聲明多個引用之后,如果都不使用的話,那么也不會出現(xiàn)錯誤。

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

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

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

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

但說實話 Rust 編譯器做的這個 "忍讓" 對于我們而言沒有太大意義,因為它要求我們聲明多個可變引用之后不能使用其中的任何一個,但問題是聲明引用就是為了使用它,不然聲明它干嘛。因此我們?nèi)钥梢哉J為:對于特定作用域中的特定數(shù)據(jù)來說,一次只能聲明一個可變引用,否則會導(dǎo)致編譯錯誤。

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

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

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

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

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

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

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

如果是編程老手的話,那么應(yīng)該會想到,如果同時存在可變引用和不可變引用會發(fā)生什么呢?我們試一下就知道了。

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

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

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

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

懸空引用

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

讓我們試著來創(chuàng)建一個懸空引用,并看一看 Rust 是如何在編譯期發(fā)現(xiàn)這個錯誤的:

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

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

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

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

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

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

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

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

小結(jié)

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

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

引用總是有效的;

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

相關(guān)文章

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

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

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

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

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

    rust閉包的使用

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

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

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

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

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

    Rust 函數(shù)詳解

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

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

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

    Rust你不認識的所有權(quán)

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

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

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

    在Rust中編寫自定義Error的詳細代碼

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

最新評論