星期六, 11月 11, 2023

Rust 的基礎 - Borrow

Rust 的基礎 - Reference 中,透過 reference 來存取到了 value。

Rust 的基礎- 所有權規則 中,有提到每個 Value 都有一個所有者 (owner)

在 Rust 中把創建一個 reference 的動作稱為借用 (borrowing)

  let hello = "hello".to_string();
  let hello_ref = &hello; // 借用 hello

為了確保 Reference 的安全,Rust 有一項編譯期檢查機制 "Borrow Checker",其用意是確保兩方面的事情

  • 所有者與 Reference 之間
  • Reference 與 Reference 之間
所有者與 Reference 之間

要確認的事項是 Rerference 不可以活得比 Owner 久,比如以下 code

   let hello = "hello".to_string();
   let hello_ref = &hello;

   drop(hello);
   println!("{}", hello_ref);

我們會得到以下錯誤

   let hello = "hello".to_string();
   let hello_ref = &hello;
                   ------ borrow of `hello` occurs here
   drop(hello);
        ^^^^^ move out of `hello` occurs here
   println!("{}", hello_ref);
                  -------- borrow later used here
Reference 與 Reference 之間

在說明 Reference 與 Reference 之間,首先先介紹 Mutable Borrowing,在介紹 Mutable Borrowing 前要先介紹什麼是 Mutable

Rust 相比其他常見的程式語言有一個特點,就是所有變數預設都是唯獨的

底下的這段 code
   let hello = "hello".to_string();
   hello = "world".to_string();
會得到編譯錯誤
   let hello = "hello".to_string();
   hello = "world".to_string();
   ^^^^^ cannot assign twice to immutable variable
想要編譯成功需要將程式改為
   let mut hello = "hello".to_string();
   hello = "world".to_string();

這麼作的思路是預設不能改可以避免潛在問題

而 Borrowing 所創建的 reference 預設是無法修改指向的 Value 的

   let mut hello = "hello".to_string();

   let hello_ref = &hello;

   hello_ref.push_str(" world");

會得到編譯錯誤

   let mut hello = "hello".to_string();

   let hello_ref = &hello;

   hello_ref.push_str(" world");
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `hello_ref` is a `&` reference, \
                                 so the data it refers to cannot be borrowed as mutable

想要修正這個錯誤,需要使用 mutable borrow

   let mut hello = "hello".to_string();

   let hello_ref = &mut hello;

   hello_ref.push_str(" world");

知道了什麼叫做 mutable borrowing 後,我們回到 Reference 與 Reference 之間 的情況,Rust 的規則是

  • borrowing 與 mutable borrowing 的 reference 不可重疊使用
  • 同時間只能有一個 mutable borrwing 的 reference

比如以下 code

   let mut hello = "hello".to_string();

   let hello_mut_ref = &mut hello;
   let hello_ref = &hello;

   hello_mut_ref.push_str(" world");
會得到編譯錯誤
   let mut hello = "hello".to_string();

   let hello_mut_ref = &mut hello;
                       ---------- mutable borrow occurs here
   let hello_ref = &hello;
                   ^^^^^^ immutable borrow occurs here

   hello_mut_ref.push_str(" world");
   -------------------------------- mutable borrow later used here

為什麼需要這種規則,可以看以下的 code

   let hello_mut_ref = &mut hello;
   let hello_ref = &hello;

   for c in hello_ref.chars() {
      hello_mut_ref.push_str(" world");
      // push_str may makes the iterator from hello_ref.chars() invalid.
      print!("{}", c);
   }

在這個範例中我們看到 hello_ref 去透過 chars function 取得了 hello 的 iterator,而在取用的途中,可以看到又透過 hello_mut_ref 在往 hello 內去附加字串。這種附加動作有可能會導致記憶體重分配,導致 hello 的 iterator 在途中變成無用的對象。

總結: Rust 的 Borrow Checker
  • 所有者與 Reference 之間
    • Reference 不可以活得比所有者久
  • Reference 與 Reference 之間
    • mutable borrowing 與 borrowing 不可同時存在
    • mutable borrowing 同時只能存在一個

以上規則可以有效防止 Dangling Pointer 的出現

沒有留言: