สรุป The Rust Programming Language ฉบับมือเก่า ตอนที่ 6
บทความนี้สรุปจากหนังสือ The Rust Programming Language chapter 6 ด้วยความคิดเห็นของผมเอง อาจมีการตีความที่มีโอกาสผิดพลาดได้ ทางที่ดีถ้าอยากได้ reference จริงๆแนะนำต้นทางครับ
Chapter 6 — Enums and Pattern Matching
เรื่องแรกเป็นเรื่องเกี่ยวกับ enums ที่ผมมองว่าผมชอบ enums ของ Rust มากๆ มันโคตร powerful และ useful
อย่างแรกคิดว่าทุกคนที่อ่านบทความนี้น่าจะชินกับ enums แบบปกติกันแล้ว โดย Rust จะเรียกแต่ละ possible value ใน enum ว่าเป็น variant
สิ่งที่เจ๋งคือ แต่ละ variant นอกจากจะเป็น value ปกติแล้ว ยังสามารถเป็น constructor ของ enum มันได้อีกด้วย ลองดูตัวอย่างโคตรด้านล่าง
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
IpAddr.V4 สามารถถูกสร้างได้โดยการเรียก IpAddr.V4(127, 0, 0, 1)
แล้วผลที่ได้ก็จะเป็น enum variant นั้นที่ associate with ตัวเลข 4 ตัวนั้น
แล้วสิ่งนี้มันมีประโยชน์ยังไง คือลองคิดว่าถ้าเราไม่มีอันนี้ เราก็ต้องไปสร้าง struct
V4 และ V6 กันเอง ปัญหาคือมันจะถูกมองว่าเป็นคนละ type เวลาเราจะรับ parameter เข้ามาใน function ของเราแล้วเราอยากจะรับได้ทั้ง 2 types มันอาจจะยาก (ผมยังอ่านไม่ถึงเลยไม่แน่ใจว่า Rust สามารถทำ union type ได้ไหม แล้วเอาจริงๆ ตอนนี้ผมมองว่า enums มันคือ union type ของ structs ด้วยซ้ำ แต่ขอดูต่อไปก่อน)
หรือเวลาทำ pattern matching ถ้าเราอยากจะ match แค่ enum type นี้ โดยไม่ต้องสน variant มันก็จะง่ายกว่าการที่เราเขียนแยกออกมาเป็น 2 structs ผมเลยมองว่ามัน amazing พอสมควรเรื่องนี้
อย่างตัวอย่างด้านล่างนี้ก็ valid ในการประกาศ enums ใน Rust
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
แล้วเราก็จะสามารถสร้าง enum instance ได้แบบนี้
let msg: Message = Message::Move { x: 3, y: 4 };
let t = match msg {
Message::Move { x, y } => (x, y),
};
หลังจากเอามา match ค่า t
ที่ได้ก็จะเป็น tuple ของ (x, y)
นอกจากนี้ Enums ยังสามารถถูก associate function ได้แบบเดียวกับ struct
เลยด้วยตามโค้ดด้านล่าง
impl Message {
fn call(&self) {
// method body would be defined here
}
}
let m = Message::Write(String::from("hello"));
m.call();
แถมว่า prelude จะมี enum ที่ชื่อ Option มาให้เลย ตัวนี้สายมือเก่าโดยเฉพาะคนที่เคยเขียน functional หรือ railway programming มาก็คงไม่ต้องพูดกันเยอะเนอะ 555
enum Option<T> {
None,
Some(T),
}
Pattern Matching
เรื่องนี้ก็จะเกี่ยวกับ match
keyword ซึ่ง… ก็คงไม่ต้องอธิบายเยอะ (อีกแล้ว 55) น่าจะรู้ๆกันอยู่แล้ว แต่มี some notes ก็แล้วกัน
- Pattern ที่ bind กับ value เช่น enum แบบข้างต้นอย่างเช่น
Option::Some(T)
จะสามารถ match แบบmatch maybe { Option::Some(v) => v }
แบบนี้แล้วค่า v ก็จะ available ใน arm ที่ matched - Matches are exhaustive หรือแปลไทยง่ายๆว่าเราต้อง handle every possible cases
- Catch-all pattern คือ
_
If let
เท่าที่ผมดู เป็นเหมือนแค่ syntax sugar ของ match ที่ handle แค่ 2 cases คือ case ที่ match specific case แล้วก็ case ที่เหลือ (ในส่วนของ case ที่เหลือจะไปอยู่ใน else
(ถ้ามี))
คือโค้ดแบบนี้
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {state:?}!"),
_ => count += 1,
}
ถ้าเขียนแบบ if let
จะเป็นแบบนี้
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {state:?}!");
} else {
count += 1;
}
ซึ่งผมบอกตามตรงว่าผมไม่ใช่ big fan กับ if let
เลย เหตุผลเพราะผมไม่ค่อยชอบอะไรที่เป็น snowflake ยกเว้น benefit ที่ได้มันคุ้มมากๆจริงๆ (ความรู้สึก ณ ตอนนี้นะครับ แต่เขียนๆไปอาจจะเปลี่ยนใจก็ได้)
จบแล้วครับบทที่ 6