เมื่อ Rust’s Default Trait พบกับปรัชญา Essentialism
ในฐานะโปรแกรมเมอร์ เรามักหมกมุ่นอยู่กับการเปลี่ยนแปลง (Mutation) เราสนใจว่า State จะไหลจาก A ไป B อย่างไร แต่เรามักลืมตั้งคำถามที่สำคัญที่สุดคำถามหนึ่ง:
“ก่อนที่สิ่งนี้จะกลายเป็นสิ่งนั้น… มัน ‘เป็น’ อะไรมาก่อน?”
นี่ไม่ใช่แค่คำถามเชิงตรรกะ แต่มันคือคำถามเชิงปรัชญาที่ลึกซึ้ง และในภาษา Rust คำตอบของคำถามนี้มักจะถูกซ่อนไว้ใน Default trait
สุนทรียศาสตร์ของความว่างเปล่า: นิยามของ ‘แก่นสาร’
ในโลกของปรัชญาตะวันตก มีแนวคิดที่เรียกว่า “Essentialism” ซึ่งเชื่อว่าสรรพสิ่งมี “แก่นสาร” (Essence) ที่ทำให้มันเป็นสิ่งนั้น หากขาดแก่นสารนี้ไป สิ่งนั้นจะสูญเสียอัตลักษณ์ของมันทันที
ลองจินตนาการถึง “เครื่องดื่ม” (Type Drink) แก่นสารของมันคืออะไร? การเป็นของเหลว? การมีรสชาติ? หรือการถูกใช้เพื่อดับกระหาย? ถ้าผมสั่ง "เครื่องดื่ม" จากบาร์เทนเดอร์โดยไม่ระบุเจตจำนง ผมกำลังเรียกหาแก่นสารที่พื้นฐานที่สุดของ Type นั้น
ใน Rust, Default trait ทำหน้าที่แบบเดียวกัน มันคือการประกาศก้องว่า "นี่คือสภาวะพื้นฐานที่สุดที่ฉันดำรงอยู่ได้"
เมื่อคุณเขียนโค้ดนี้:
impl Default for Drink {
fn default() -> Self {
Self {
name: String::from("เหล้าขม"),
cost: 0,
}
}
}
คุณไม่ได้แค่เขียน Function เพื่อคืนค่าเริ่มต้น แต่คุณกำลังนิยามว่า “เครื่องดื่มจำลองในบาร์แห่งนี้มีแก่นแท้คือความขมขื่นที่ราคาถูกที่สุด”
ภัยร้ายของการมีตัวตนโดยบังเอิญ
ความมักง่ายมักนำไปสู่จุดจบที่ไม่สวยงาม นักพัฒนารุ่นใหม่หลายคนเผลอใช้ #[derive(Default)]แปะไว้บนหัว Struct โดยไม่คิดถึงแก่นสารของมัน
#[derive(Default)]
struct User {
id: u64,
name: String,
role: String,
}
ถ้าตาม Default ของ Rust ค่า id จะเป็น 0, name จะเป็น "" (String ว่าง), และ roleก็จะเป็น "" ... นี่คือการสร้าง "มนุษย์ไร้วิญญาณ" ขึ้นมาในระบบของคุณ User คนนี้ดำรงอยู่ได้โดยไม่มี ID ไม่มีชื่อ ไม่มีบทบาท มันคือขยะ (Garbage Data) ที่ถูกต้องตามกฎของ Type System แต่ผิดต่อกฎของความเป็นจริง
Essentialism สอนให้เราแยกแยะระหว่าง “สิ่งที่ติดตัวมาแต่กำเนิด” กับ “สิ่งที่โลกปรุงแต่งทีหลัง” หากสิ่งใดต้องรอ Input จากภายนอก สิ่งนั้นไม่ใช่แก่นสาร
Option และ None: แก่นสารของความไม่แน่นอน
Rust ออกแบบมาอย่างชาญฉลาดในเรื่องนี้ ลองดูที่ Option<T> แก่นสารของมันคืออะไร? ไม่ใช่ค่า Some ที่ห่อหุ้มข้อมูลมหาศาลไว้ แต่มันคือ None ความว่างเปล่าคือจุดเริ่มต้นของมัน
impl Default for Option<T> คืนค่า None เสมอ... นี่คือสภาวะพื้นฐานที่ปลอดภัยที่สุด มันคือการยอมรับว่า "ในจุดเริ่มต้น ฉันคือความไม่แน่นอน"
เช่นเดียวกับ Vec<T> แก่นสารของมันไม่ใช่ List ที่เต็มไปด้วยข้อมูลหมื่นแถว แต่มันคือ Vec::new() คือพื้นที่ว่างที่พร้อมจะรับข้อมูล การกำหนด Default แบบนี้คือการพา Type นั้นกลับไปที่ "จุดศูนย์" (Zero Point) เพื่อยืนยันว่า ต่อให้ไม่มี Input ใดๆ เลย ฉันก็ยังมีที่ยืนในหน่วยความจำ
สู่การออกแบบที่ชัดเจน
ดังนั้น ครั้งต่อไปที่คุณกำลังออกแบบ Domain Model ใน Rust ขอให้หยุดคิดสักนิด:
- อย่าปล่อยให้ Machine กำหนดแก่นสารแทนคุณ: จงหลีกเลี่ยงการใช้
#[derive(Default)]สำหรับ Struct ที่มี Logic ซับซ้อน ถ้าคุณต้องการ Default จงimpl Defaultด้วยมือของคุณเอง (Manual Implementation) - ถามคำถามสุดท้าย: ก่อนที่คุณจะสั่ง
..Default::default()เพื่อเติมเต็มช่องว่างใน Struct ให้ถามตัวเองว่า "ถ้าโลกนี้ล่มสลายเหลือเพียงค่านี้ ค่านี้ยังนิยามสิ่งที่ฉันสร้างได้อยู่ไหม?" หากคำตอบคือไม่ สิ่งนั้นไม่ควรมี Default - เคารพความบริสุทธิ์: ถ้า Type ของคุณไม่มีสภาวะเริ่มต้นที่สมเหตุสมผล ก็อย่าพยายามสร้างมันขึ้นมา เพียงเพื่อให้โค้ดเขียนง่ายขึ้น มันคือการบิดเบือนแก่นสาร และนั่นคือจุดเริ่มต้นของ Bug ที่แก้ยากที่สุด
ในโลกที่ซับซ้อนและเต็มไปด้วยความผิดพลาด… การมี “แก่นสาร” ที่ชัดเจนและปลอดภัย คือสิ่งเดียวที่ช่วยให้สถาปัตยกรรมของคุณรอดพ้นจากความโกลาหล
ขอให้ 0 ของคุณ คือศูนย์ที่มีความหมาย
เกี่ยวกับผู้เขียน: Uncle Quin — Software Engineer ผู้หลงใหลในการเชื่อมโยงวิศวกรรมซอฟต์แวร์เข้ากับปรัชญา
หากบทความนี้มีประโยชน์
คุณสามารถติดตาม Late Night with Uncle Quin ได้ทาง
ที่ที่เราคุยกันเรื่อง software, engineering mindset และอนาคตของ developer
แบบไม่ต้องใส่สูท
แต่ใส่ความจริงของวงการเข้าไปเต็ม ๆ