網(wǎng)站首頁 編程語言 正文
引用和借用
如果每次都發(fā)生所有權(quán)的轉(zhuǎn)移,程序的編寫就會變得異常復(fù)雜。因此rust和其它編程語言類似,提供了引用的方式來操作。獲取變量的引用,稱為借用。類似于你借別人的東西來使用,但是這個東西的所有者不是你。引用不會發(fā)生所有權(quán)的轉(zhuǎn)移。
引用的使用
在rust中,引用的語法非常簡單。通過&來取引用,通過*來解引用。例如:
fn main() { let s1: String = "Hello".to_string(); let s2: &String = &s1; // s2引用s1 println!("{s1}"); println!("{s2}"); }
這段代碼可以正常運行,因為s2引用的s1,不會發(fā)生所有權(quán)的轉(zhuǎn)移。再來看一個例子,通過引用來傳遞函數(shù)參數(shù)。
fn main() { let s = "Hello".to_string(); let len = calculate_length(&s); // 引用 println!("{s}"); println!("{len}"); } fn calculate_length(s: &String) -> usize { s.len() }
在calculate_length中,s是一個引用,它不具備所有權(quán),因此在函數(shù)調(diào)用結(jié)束的時候,s的作用域雖然結(jié)束了,但是不會調(diào)用drop。
可變引用與不可變引用
在剛才的例子中,只是獲取了字符串的長度,相當于我們讀取了變量。在rust中,引用默認也是不可變的,如果需要通過引用修改變量,那么必須使用可變引用??勺円煤涂勺冏兞恳粯樱际峭ㄟ^關(guān)鍵字mut來實現(xiàn)的。例如:
fn main() { let mut s = String::from("hello"); change(&mut s); // 可變引用 println!("{s}"); } fn change(some_string: &mut String) { some_string.push_str(", world"); }
這段代碼輸出hello, world
,可見我們通過可變引用修改了s的值,但是在這個過程中并沒有涉及所有權(quán)的轉(zhuǎn)移。
事實上,事情并沒有這么簡單??勺円貌⒉皇强梢噪S心所欲的被使用。它有一個很大的限制,“同一作用域,一個變量只能有一個可變引用”。例如:
fn main() { let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; // 同一作用域,無法創(chuàng)建兩個可變引用。 println!("{}, {}", r1, r2); }
兩個可變引用,可能會出現(xiàn)“同時寫入”這種情況,導(dǎo)致內(nèi)存不安全的情形發(fā)生。如果在不同的作用域,可以有多個可變引用,但是它們不能同時被擁有。例如:
fn main() { let mut s = String::from("hello"); { let r1 = &mut s; println!("{r1}"); } // r1 在這里離開了作用域,所以我們完全可以創(chuàng)建一個新的引用 let r2 = &mut s; println!("{r2}"); }
同時rust也不允許同時存在可變引用和不可變引用。因為不可變引用可能會因可變引用變得失效。下面以一段C++代碼來說明這一點。
#include<vector> #include<string> #include<iostream> using namespace std; int main() { // 可讀引用因可變引用而變得失效 vector<string> vs; vs.push_back("hello"); auto & elem = vs[0]; vs.push_back("world"); // push_back會導(dǎo)致vs指向的整段內(nèi)存被重新分配并移到了另一個地址,原本迭代器里面的引用就全部變成懸垂指針了。 cout << vs[0] << endl; cout << elem << endl; // 試圖使用懸垂指針 return 0; }
這段代碼執(zhí)行之后,結(jié)果如下所示:
hello
Segmentation fault (core dumped)
很明顯,這里的段錯誤正是由于試圖使用懸垂指針引起的。而rust特殊的可變引用和不可變引用機制避免了這種錯誤的發(fā)生。例如:
fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { let s = String::from("hello"); &s } // 返回s的引用,函數(shù)結(jié)束,s移出作用域,調(diào)用drop函數(shù)清理內(nèi)存,那么返回的引用將會變成懸垂引用,從而引發(fā)錯誤。
這段rust代碼無法編譯通過,從而避免了像上面C++代碼那樣的運行時錯誤。
正如Rust 程序設(shè)計語言中所言
這一限制以一種非常小心謹慎的方式允許可變性,防止同一時間對同一數(shù)據(jù)存在多個可變引用。新 Rustacean 們經(jīng)常難以適應(yīng)這一點,因為大部分語言中變量任何時候都是可變的。這個限制的好處是 Rust 可以在編譯時就避免數(shù)據(jù)競爭。數(shù)據(jù)競爭(data race)類似于競態(tài)條件,它可由這三個行為造成:
兩個或更多指針同時訪問同一數(shù)據(jù)。
至少有一個指針被用來寫入數(shù)據(jù)。
沒有同步數(shù)據(jù)訪問的機制。
Rust 的編譯器一直在優(yōu)化,早期的時候,引用的作用域跟變量作用域是一致的,這對日常使用帶來了很大的困擾,你必須非常小心的去安排可變、不可變變量的借用,免得無法通過編譯,例如以下代碼:
fn main() { let mut s = String::from("hello"); let r1 = &s; let r2 = &s; println!("{} and {}", r1, r2); // 新編譯器中,r1,r2作用域在這里結(jié)束 let r3 = &mut s; println!("{}", r3); } // 老編譯器中,r1、r2、r3作用域在這里結(jié)束 // 新編譯器中,r3作用域在這里結(jié)束
在老版本的編譯器中(Rust 1.31 前),將會報錯,因為 r1 和 r2 的作用域在花括號 } 處結(jié)束,那么 r3 的借用就會觸發(fā) 無法同時借用可變和不可變的規(guī)則。但是在新的編譯器中,該代碼將順利通過,因為 引用作用域的結(jié)束位置從花括號變成最后一次使用的位置,因此 r1 借用和 r2 借用在 println! 后,就結(jié)束了,此時 r3 可以順利借用到可變引用。
NLL
對于這種編譯器優(yōu)化行為,Rust 專門起了一個名字 —— Non-Lexical Lifetimes(NLL),專門用于找到某個引用在作用域(})結(jié)束前就不再被使用的代碼位置。
總結(jié)
- 總的來說,借用規(guī)則如下:
- 同一時刻,你只能擁有要么一個可變引用, 要么任意多個不可變引用引用必須總是有效的 參考資料
Rust 程序設(shè)計語言
Rust單線程下為什么還是只能有一個可變引用呢?
Rust語言圣經(jīng)
原文鏈接:https://blog.csdn.net/zy010101/article/details/128498239
相關(guān)推薦
- 2022-10-17 一文教會你用nginx+uwsgi部署自己的django項目_python
- 2022-04-21 新一代Python包管理工具_python
- 2022-03-28 Python中selenium_webdriver下拉框操作指南_python
- 2022-10-30 Python利用Pandas進行數(shù)據(jù)分析的方法詳解_python
- 2022-07-15 go?GCM?gin中間件的加密解密文件流處理_Golang
- 2022-09-23 Pandas多列值合并成一列的實現(xiàn)_python
- 2022-09-24 一文詳解go?mod依賴管理詳情_Golang
- 2022-03-14 has been blocked by CORS policy: Response to prefl
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學(xué)習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支