星期六, 11月 04, 2023

Rust 的基礎 - Reference

在之前的所有權章節有提到以下的 code 行為是 move 而非複製

let hello = "Hello World".to_string(); let hello2 = hello; // hello move to hello2

在 Rust 中可以定義 function

fn add(v1: i32, v2: i32) -> i32{
  return v1 + v2;
}
在 move 的規則之下,在需要保留原本傳入參數的情況下。就得將原本的參數再回傳回來
fn concat(s1: String, s2: String) -> (String, String, String) {
   let result = format!("{}{}", s1, s2);

   return (s1, s2, result);
}

fn main() {
   let hello = "Hello".to_string();
   let world = "World".to_string();

   let (hello, world, hello_world) = concat(hello, world);

   println!("{} = {} + {}", hello_world, hello, world);
}

這種作法顯然會帶來兩個問題

  1. 撰寫麻煩
  2. 額外的 Stack 複製成本

為此 Rust 中引入了 reference 機制,以下為使用 Reference 的機制改寫的 code

fn concat(s1: &String, s2: &String) -> String {
   return format!("{}{}", s1, s2);
}

fn main() {
   let hello = "Hello".to_string();
   let world = "World".to_string();

   let hello_world = concat(&hello, &world);

   println!("{} = {} + {}", hello_world, hello, world);
}

在上述的範例中,主要做了兩項改變

  1. concat 參數的型別由String變為&String
  2. concat 傳入的變數由hello變為&hello

&String翻譯為中文的話是 "一個 Reference,指向的型別是 String"

&hello的話,& 則是一個運算子,對 hello 運算後會得到指向 hello 的 Reference

以下的 code 我們可以得到 String 與 &String 在記憶體中的大小

fn main() {
   println!("{}", std::mem::size_of::<String>());
   println!("{}", std::mem::size_of::<&String>());
}

在 x64 環境下,String 大小是 24 bytes,&String 大小則是 8 bytes,而 x86 環境下則分別是 12 bytes 與 4 bytes

可以發現 reference 的大小規則與所在平台的記憶體空間的所需定位大小一致,基本上就是 C 的指標

一旦有了指標出現,就會想到一些安全問題,比如 Dangling Pointer等,在之後的文章會介紹 Rust 如何防止 Rerference 的問題

沒有留言: