107.05.12 rust 之路 09 參考&借用

題外話,Rust 1.26.0 在 5/10 發布啦 OuO

借用 (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. 同個有效範圍內有可多個 & (參考)
2. 同個有效範圍內有最多一個 &mut (可變參考)
3. 同個有效範圍 &mut、& 不可同時存在
4. 參考、可變參考必須一定有效
上次的 code 這裡不行是因為 s2 拿走了 "OuO" 這個 String 的所有權
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
                                   // 危險!💀

沒有留言:

張貼留言

^ Top