可變參考

YouTube 上觀看本章內容

如果你想使用參考來改變資料,你可以使用可變參考(mutable reference)。可變參考你要寫做 &mut 而不是 &

fn main() {
    let mut my_number = 8; // 這裡不要忘記寫 mut!
    let num_ref = &mut my_number;
}

那麼這兩種型別是什麼呢?my_numberi32,而 num_ref&mut i32(我們讀作 "可變參考 i32")。

那麼讓我們用它來給 my_number 加上 10。但是你不能寫 num_ref += 10,因為 num_ref 不是 i32 的值,它是 &i32。其實這個值就在 i32 裡面。為了達到值所在的地方,我們用 ** 的意思是"我不要參考,我想要參考所參照的值"。換句話說,*& 是相反的動作。也就是一個 * 消去了一個 &

fn main() {
    let mut my_number = 8;
    let num_ref = &mut my_number;
    *num_ref += 10; // 使用 * 來改變 i32 的值.
    println!("{}", my_number);

    let second_number = 800;
    let triple_reference = &&&second_number;
    println!("Second_number = triple_reference? {}", second_number == ***triple_reference);
}

印出:

18
Second_number = triple_reference? true

因為使用 & 時叫做 "參考",所以用 * 叫做 "參考(dereferencing)"。

Rust在可變和不可變參考有兩個規則。它們非常重要卻也容易記住,因為它們很有道理。

  • 規則1:如果你只有不可變參考,你可以同時有任意多的參考。1 個也好,3 個也好,1000 個也好,都沒問題。
  • 規則2:如果是可變參考,你只能有一個。另外,你不能同時有一個不可變參考一個可變參考。

這是因為可變參考能變更資料。如果你在其他參考讀取資料時更改資料,你可能會遇到問題。

理解的好方法是設想一場 Powerpoint 簡報。

情境一是關於只有一個可變參考

情境一: 一位員工正在編寫一個 Powerpoint 簡報,他希望他的經理能幫助他。該員工將自己的登入資訊提供給經理,並請他幫忙進行編輯。現在經理對該員工的簡報有了"可變參考"。經理可以做任何他想做的修改,然後把電腦還回去。這很好,因為沒有其他人看得到這個簡報。

情境二是關於只有不可變參考

情境二: 該員工要給100個人做簡報。現在這100個人都可以看到該員工的資料。他們全都有對該員工簡報的"不可變參考"。這很好,因為他們可以看得到,但沒人可以改動資料。

情境三是有問題的情形

情境三: 員工把他的登入資訊給了經理 他的經理現在有了一個 "可變參考"。然後該員工去給 100 個人做簡報,但是經理還是可以登入。這是不對的,因為經理可以登入,可以做任何事情。也許他的經理會登入電腦,然後開始給他的母親打一封信!現在這 100 人不得不看著經理給他母親寫信,而不是簡報。這不是他們期望看到的。

這裡有一個可變借用借用自不可變借用的範例:

fn main() {
    let mut number = 10;
    let number_ref = &number;
    let number_change = &mut number;
    *number_change += 10;
    println!("{}", number_ref); // ⚠️
}

編譯器印出了一則有用的資訊來告訴我們問題所在。

error[E0502]: cannot borrow `number` as mutable because it is also borrowed as immutable
 --> src\main.rs:4:25
  |
3 |     let number_ref = &number;
  |                      ------- immutable borrow occurs here
4 |     let number_change = &mut number;
  |                         ^^^^^^^^^^^ mutable borrow occurs here
5 |     *number_change += 10;
6 |     println!("{}", number_ref);
  |                    ---------- immutable borrow later used here

然而,這段程式碼可以運作。為什麼?

fn main() {
    let mut number = 10;
    let number_change = &mut number; // 建立可變借用
    *number_change += 10; // 用可變借用來加上 10
    let number_ref = &number; // 建立不可變借用
    println!("{}", number_ref); // 印出不可變借用
}

它印出 20 沒有問題。它能運作是因為編譯器夠聰明,能理解我們的程式碼。它知道我們使用了 number_change 來改變 number,但沒有再使用它。所以這裡沒有問題。我們並沒有將不可變和可變參考一起使用。

早期在 Rust 中,這種程式碼實際上會產生錯誤,但現在的編譯器更聰明了。它不僅能理解我們輸入的內容,還能理解我們如何使用所有的東西。

再談遮蔽

還記得我們說過,遮蔽(shadowing)不會銷毀一個值,而是阻擋它嗎?現在我們可以用參考來看這個問題。

fn main() {
    let country = String::from("Austria");
    let country_ref = &country;
    let country = 8;
    println!("{}, {}", country_ref, country);
}

這會印出 Austria, 8 還是 8, 8?它印出的是 Austria, 8。首先我們宣告一個 String,叫做 country。然後我們給這個字串建立一個參考 country_ref。然後我們用 8,這是 i32,來遮蔽 country。但是第一個 country 並沒有被銷毀,所以 country_ref 仍然參照著 "Austria",而不是 "8"。這是同樣的程式碼附上了一些註解來說明它如何運作:

fn main() {
    let country = String::from("Austria"); // 現在我們有個 String 叫作 country
    let country_ref = &country; // country_ref 是這筆資料的參考。它不會改動
    let country = 8; // 現在我們有個變數叫作 country 型別是 i8。但它和另一個變數或 country_ref 沒有關聯
    println!("{}, {}", country_ref, country); // country_ref 仍然參照自我們給的 String::from("Austria") 的資料.
}