日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

rust引用和借用的使用小結_Rust語言

作者:zy010101 ? 更新時間: 2023-02-10 編程語言

引用和借用

如果每次都發生所有權的轉移,程序的編寫就會變得異常復雜。因此rust和其它編程語言類似,提供了引用的方式來操作。獲取變量的引用,稱為借用。類似于你借別人的東西來使用,但是這個東西的所有者不是你。引用不會發生所有權的轉移

引用的使用

在rust中,引用的語法非常簡單。通過&來取引用,通過*來解引用。例如:

fn main() {
    let s1: String = "Hello".to_string();
    let s2: &String = &s1;       // s2引用s1

    println!("{s1}");
    println!("{s2}");
}

這段代碼可以正常運行,因為s2引用的s1,不會發生所有權的轉移。再來看一個例子,通過引用來傳遞函數參數。

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是一個引用,它不具備所有權,因此在函數調用結束的時候,s的作用域雖然結束了,但是不會調用drop。

可變引用與不可變引用

在剛才的例子中,只是獲取了字符串的長度,相當于我們讀取了變量。在rust中,引用默認也是不可變的,如果需要通過引用修改變量,那么必須使用可變引用。可變引用和可變變量一樣,都是通過關鍵字mut來實現的。例如:

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的值,但是在這個過程中并沒有涉及所有權的轉移。

事實上,事情并沒有這么簡單。可變引用并不是可以隨心所欲的被使用。它有一個很大的限制,“同一作用域,一個變量只能有一個可變引用”。例如:

fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s;     // 同一作用域,無法創建兩個可變引用。

    println!("{}, {}", r1, r2);
}

兩個可變引用,可能會出現“同時寫入”這種情況,導致內存不安全的情形發生。如果在不同的作用域,可以有多個可變引用,但是它們不能同時被擁有。例如:

fn main() {
    let mut s = String::from("hello");

    {
        let r1 = &mut s;
        println!("{r1}");
    } // r1 在這里離開了作用域,所以我們完全可以創建一個新的引用

    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會導致vs指向的整段內存被重新分配并移到了另一個地址,原本迭代器里面的引用就全部變成懸垂指針了。

    cout << vs[0] << endl;
    cout << elem << endl;       // 試圖使用懸垂指針
    
    return 0;
}

這段代碼執行之后,結果如下所示:

hello
Segmentation fault (core dumped)

很明顯,這里的段錯誤正是由于試圖使用懸垂指針引起的。而rust特殊的可變引用和不可變引用機制避免了這種錯誤的發生。例如:

fn main() {
    let reference_to_nothing = dangle();
}

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

    &s
}   // 返回s的引用,函數結束,s移出作用域,調用drop函數清理內存,那么返回的引用將會變成懸垂引用,從而引發錯誤。

這段rust代碼無法編譯通過,從而避免了像上面C++代碼那樣的運行時錯誤。

正如Rust 程序設計語言中所言

這一限制以一種非常小心謹慎的方式允許可變性,防止同一時間對同一數據存在多個可變引用。新 Rustacean 們經常難以適應這一點,因為大部分語言中變量任何時候都是可變的。這個限制的好處是 Rust 可以在編譯時就避免數據競爭。數據競爭(data race)類似于競態條件,它可由這三個行為造成:

兩個或更多指針同時訪問同一數據。
至少有一個指針被用來寫入數據。
沒有同步數據訪問的機制。

Rust 的編譯器一直在優化,早期的時候,引用的作用域跟變量作用域是一致的,這對日常使用帶來了很大的困擾,你必須非常小心的去安排可變、不可變變量的借用,免得無法通過編譯,例如以下代碼:

fn main() {
   let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;
    println!("{} and {}", r1, r2);
    // 新編譯器中,r1,r2作用域在這里結束

    let r3 = &mut s;
    println!("{}", r3);
} // 老編譯器中,r1、r2、r3作用域在這里結束
  // 新編譯器中,r3作用域在這里結束

在老版本的編譯器中(Rust 1.31 前),將會報錯,因為 r1 和 r2 的作用域在花括號 } 處結束,那么 r3 的借用就會觸發 無法同時借用可變和不可變的規則。但是在新的編譯器中,該代碼將順利通過,因為 引用作用域的結束位置從花括號變成最后一次使用的位置,因此 r1 借用和 r2 借用在 println! 后,就結束了,此時 r3 可以順利借用到可變引用。

NLL

對于這種編譯器優化行為,Rust 專門起了一個名字 —— Non-Lexical Lifetimes(NLL),專門用于找到某個引用在作用域(})結束前就不再被使用的代碼位置。

總結

  • 總的來說,借用規則如下:
  • 同一時刻,你只能擁有要么一個可變引用, 要么任意多個不可變引用引用必須總是有效的 參考資料

Rust 程序設計語言
Rust單線程下為什么還是只能有一個可變引用呢?
Rust語言圣經

原文鏈接:https://blog.csdn.net/zy010101/article/details/128498239

欄目分類
最近更新