借用 (Borrowing) 主要就是參考 (Reference) 概念的包裝
跟 C++ 的 reference 概念相同,差在寫法上
只不過我覺得 Rust 的寫法會讓程式碼要傳達的意思比較清楚
捲走你以為放得下的思念,然後又被海浪給還了回來
Photo by Spencer Watson on Unsplash |
比對一下寫法
C++:
int x = 10; int &r = x; r += 10; cout << r << endl; // 20
Rust:
let mut x = 10; let r = &mut x; // 型別為 &mut i32 但可省略不寫 *r += 10; println!("{}", *r); // 20
知道哪邊比較清楚了吧
主要是因為 C++ 是隱含轉換,所以在給 r 值時不需要說是 x 的參考
而在 Rust 中很容易就可以看出,r 存的是 x 的參考 "&x",而操作時使用 "*" (dereference 運算子)解參考
在 Rust 的範例中使用 mut 是因為要跟 C++ 作相同功能的對照,否則就不能做 +=,以下為對應
Rust: <--> C++: let r = &mut x; <--> int &r = x; let r = &x; <--> const int &r = x;
參考 (Reference)
關鍵字:& (ampersand /`æmpɚs,ænd/) ....好像變成正音班....最重要的價值就是不會拿走所有權
使用規則:
1. 同個有效範圍內有可多個 & (參考)上次的 code 這裡不行是因為 s2 拿走了 "OuO" 這個 String 的所有權
2. 同個有效範圍內有最多一個 &mut (可變參考)
3. 同個有效範圍 &mut、& 不可同時存在
4. 參考、可變參考必須一定有效
let s1 = String::from("OuO"); let s2 = s1; println!("{}", s1); // error,因為 s1 的值已經移給 s2
改成使用 reference,所有權還是在 s1
let s1 = String::from("OuO"); let s2 = &s1; println!("{}", s1); // OuO println!("{}", s2); // OuO....等一下
別以為我沒發現喔,為啥這次範例中的 s2 前面不需要加 * (dereference)
因為....
automatic referencing and dereferencing
因為這個神奇的特性所以 Rust 就不用像 C++ 使用 -> 運算子程式碼也更乾淨
let s1 = String::from("OuO"); let s2 = &&&&&s1; // s2 -> x -> y -> z -> t -> s1 println!("{}", *****s2); // OuO println!("{}", s2); // OuO, amazing auto-dereference!這個主要是靠 self 的型別 (Self、&Self、&mut Self)
Rust 會自動在變數前加上 &、&mut、* 直到找到可用的方法
之後在 method 的部分會再提
-
What are Rust's exact auto-dereferencing rules?
借用 (Borrowing)
在一般的呼叫函式傳入參數時其實也會將所有權傳入e.g.
fn main() { let s1 = String::from("OuO"); take_ownership(s1); println!("{}", s1); // error --> use of moved value: `s1` } fn take_ownership(s: String) { // 從 s1 拿走所有權 println!("{}", s); }
有一種解法是用完後傳回所有權
不過非常不建議,整個程式流程變很怪,而且可讀性差
fn take_ownership(s: String) -> String { println!("{}", s); s } // 呼叫:let s1 = take_ownership_and_return(s1);
正解:
這時候就可以利用參考的特性了因為傳入函式的參數只是參考,所以所有權並不會轉移
這種參數為參考的用法就叫做借用 (Borrowing)
fn main() { let s1 = String::from("OuO"); borrow(&s1); // 記得要傳參考 println!("{}", s1); // OuO } fn borrow(s: &String) { println!("{}", s); // OuO }
當然也可以傳可變參考,這樣在函式中就可以更改變數的值
fn main() { let mut s1 = String::from("OuO"); borrow_and_change(&mut s1); println!("{}", s1); // OuO~~ } fn borrow_and_change(s: &mut String) { s.push_str("~~"); println!("{}", s); // OuO~~ }
Dangling References
當然因為參考一定要有效,所以下面這種用法直接會被編譯器擋下來fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { // 回傳 String 的參考 let s = String::from("hello"); // s 是新字串 &s // 把 s 的參考當作回傳值 } // s 離開有效範圍,自動 drop // 危險!💀
沒有留言:
張貼留言