Rust 學習筆記:結構體
2023年10月7日 · 中文
前言
本系列文書寫我個人初探 Rust 的學習筆記,章節劃分主要基於著名的 The Book,The Rust Programming Language,程式碼部分通常是個人閱讀消化後以方便說明的方式撰寫,完整學習建議直接參見該書。
The Rust Programming Language
該書也有中文翻譯版,不過個人閱讀以英文原版為主以鞏固對 terminology 的一致認識,我認為對未來閱讀以及查找資料會較為順暢。
不論語言該書都是相當優秀的學習資源,選擇適合你的語言開始學習 Rust 吧。
Appendix F: Translations of the Book
結構體 struct
Rust 並不像 C++ 或 Java 提供物件導向的 class
語法,Rust 中使用 struct
或 enum
配合 impl
區塊來自定義類型結構與 method,這篇先來學習 struct
。
struct 定義
定義 struct 的語法如下。
struct Person { name: String, height: u32, weight: u32 }
在 main function 裡建立一個 Person
的 instance,給定 key-value pair,這邊使用了 derive
給了 Person
Debug trait,這個 concept 後面會學習到,在這邊只是用來讓我可以把 struct print 出來而已。
#[derive(Debug)] struct Person { name: String, height: u32, weight: u32 } fn main() { let p = Person { name: String::from("Josh"), height: 180, weight: 80 }; println!("{:?}", p); // Person { name: "Josh", height: 180, weight: 80 } }
取得 struct 中的值
可以用 dot notation 來取用 struct instance 中的值。
println!("{}", p.name); // Josh
可變結構
一樣可以用 mut
讓 struct 可變,要注意的是 struct 必須是整個可變,我們不能僅讓其中一個 field 可變。
let mut p = Person{ name: String::from("Josh"), height: 180, weight: 80 }; println!("{}", p.height); // 180 p.height += 10; println!("{}", p.height); // 190
Struct Update Syntax
在 Rust 中可以運用所謂的 Struct Update Syntax 來利用存在的 instance 來建立新的 instance,並保留其部分設定值。語法為使用 ..
。
學過 Javascript 的看起來會感覺很像 spread operator 的用法。
let p = Person{ name: String::from("Josh"), height: 180, weight: 80 }; let p2 = Person { name: String::from("Peter"), ..p }; println!("{:?}", p); // Person { name: "Josh", height: 180, weight: 80 } println!("{:?}", p2); // Person { name: "Peter", height: 180, weight: 80 }
Tuple structs
struct 也可以沒有 key,看起來會很像 tuple,主要用途在於為結構命名。
struct Point(i32, i32, i32);
Unit-like structs
struct 也可以完全沒有欄位,很像 unit type ()
。
struct Unit;
實作 method
我們使用 impl
來建立 Person 的 implementation block,在裡面可以實作 method。
struct Person { name: String, height: u32, weight: u32 } impl Person { fn print_name(&self) { println!("{}", self.name); } } fn main() { let p = Person{ name: String::from("Josh"), height: 180, weight: 80 }; p.print_name(); // Josh }
這邊的 &self
是 self: &self
的簡寫,method 的第一個參數必須是名為 self
的 Self
型別。
self
也不一定需要是參考,在部分情況需要時一樣可以不使用參考而取得 self
的所有權。
在 impl block 中可以實作複數個 method,這邊再寫一個計算 bmi 的 method。
struct Person { name: String, height: u32, weight: u32 } impl Person { fn print_name(&self) { println!("{}", self.name); } fn get_bmi(&self) -> f64 { let h = ((self.height as f64) / 100.0) * ((self.height as f64) / 100.0); (self.weight as f64) / h } } fn main() { let p = Person{ name: String::from("Josh"), height: 180, weight: 80 }; println!("{:?}", p.get_bmi()); // 24.691358024691358 }
Associated Functions
若不需要自己的 instance 的話,可以撰寫第一個參數不是 self 的 function,這種 function 在 Rust 中被稱為 Associated Functions,他們不被稱作 methods,因此我們才會說 method 的第一個參數必須是名為 self
的 Self
型別。
struct 可以用複數個 impl block,這裡我就分開一個 block。
struct Person { name: String, height: u32, weight: u32 } impl Person { fn print_name(&self) { println!("{}", self.name); } fn get_bmi(&self) -> f64 { let h = ((self.height as f64) / 100.0) * ((self.height as f64) / 100.0); (self.weight as f64) / h } } impl Person { // Associated Function fn say_hello() { println!("{}", "Hello"); } } fn main() { let p = Person{ name: String::from("Josh"), height: 180, weight: 80 }; Person::say_hello(); // Hello println!("{:?}", p.get_bmi()); }
使用 Associated Function 的語法是 ::
,我們在建立 String
時使用 From
經常用到。
事實上這也是 Associated Function 最常見的 use case,用來產生新的 struct instance。
我們可以為 Person
實作 new
function 來建立新的 instance。
#[derive(Debug)] struct Person { name: String, height: u32, weight: u32 } impl Person { fn print_name(&self) { println!("{}", self.name); } fn get_bmi(&self) -> f64 { let h = ((self.height as f64) / 100.0) * ((self.height as f64) / 100.0); (self.weight as f64) / h } } impl Person { // Associated Functions fn say_hello() { println!("{}", "Hello"); } fn new(name: String, height: u32, weight: u32) -> Person { Person { name, height, weight } } } fn main() { let p = Person::new(String::from("Josh"), 180u32, 80u32); println!("{:?}", p); // Person { name: "Josh", height: 180, weight: 80 } }
是不是更像建立 String
的用法了 ?
這邊還用到了一個特性,在 key 和 value 同名時可以省略,而不需要寫成 name: name
,這如果是寫過 Javascript 的應該會很熟悉這個方便的寫法。
fn new(name: String, height: u32, weight: u32) -> Person { Person { name, height, weight } }
下一篇會進入 enum
囉。