可變參考
如果你想使用參考來改變資料,你可以使用可變參考(mutable reference)。可變參考你要寫做 &mut
而不是 &
。
fn main() { let mut my_number = 8; // 這裡不要忘記寫 mut! let num_ref = &mut my_number; }
那麼這兩種型別是什麼呢?my_number
是 i32
,而 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") 的資料. }