Understanding Traits, Generics และการเขียนโค้ดแบบ Polymorphism

Sharing is caring!

Rust เป็นภาษาที่เน้นความปลอดภัยของหน่วยความจำและประสิทธิภาพสูง โดยยังคงความยืดหยุ่นผ่านระบบ Trait, Generics และการทำงานแบบ Polymorphism ซึ่งช่วยให้เราสามารถเขียนโค้ดที่นำกลับมาใช้ซ้ำได้ (Reusable Code) และยัง maintain ได้ง่าย

Trait คืออะไร?

Trait ใน Rust มีลักษณะคล้าย interface ในภาษาอื่น ๆ เช่น Java หรือ TypeScript โดยกำหนดพฤติกรรม (Behavior) ที่ struct ต้อง implement:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> String {
        String::from("Woof!")
    }
}

impl Speak for Cat {
    fn speak(&self) -> String {
        String::from("Meow!")
    }
}

เราสามารถเขียนฟังก์ชันที่รับ parameter ใด ๆ ก็ตามที่ implement Trait ได้:

fn animal_sound<T: Speak>(animal: T) {
    println!("{}", animal.speak());
}

Generic คืออะไร?

Generic ช่วยให้เราสามารถเขียนโค้ดที่ทำงานได้กับหลายประเภทข้อมูล โดยไม่ต้องเขียนซ้ำ:

fn print_twice<T: std::fmt::Display>(x: T) {
    println!("{}", x);
    println!("{}", x);
}

สามารถใช้กับ struct ได้ด้วย:

struct Point<T> {
    x: T,
    y: T,
}

let int_point = Point { x: 1, y: 2 };
let float_point = Point { x: 1.5, y: 2.8 };

Polymorphism แบบ Static และ Dynamic

Polymorphism คือความสามารถในการใช้โค้ดเดียวกันกับ object ที่มีรูปแบบต่างกัน ใน Rust มี 2 รูปแบบ:

  • Static Dispatch: ผ่าน generics เช่น <T: Trait> ซึ่ง compiler จะเลือก implement ตอน compile-time
  • Dynamic Dispatch: ผ่าน dyn Trait ซึ่งใช้ pointer และ runtime lookup
fn make_speak(animal: &dyn Speak) {
    println!("{}", animal.speak());
}

let dog = Dog;
make_speak(&dog);

Trait Bound และ where clause

เมื่อใช้หลาย Trait พร้อมกันสามารถเขียนได้ทั้งแบบ inline และ where clause:

// แบบ inline
fn print_json<T: Serialize + Debug>(item: T) {
    println!("{:?}", item);
}

// แบบ where
fn print_json2<T>(item: T)
where T: Serialize + Debug
{
    println!("{:?}", item);
}

Associated Types กับ Traits

สามารถกำหนดชนิดข้อมูลภายใน Trait ได้เช่นกัน:

trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

Object Safety และ dyn Trait

หากต้องการใช้ Trait เป็น Trait Object (เช่น Box<dyn Trait>) Trait นั้นต้องเป็น “object-safe” ซึ่งหมายถึงต้องไม่มี generic method และต้องรับ &self หรือ &mut self เท่านั้น

trait Drawable {
    fn draw(&self);
}

fn render(obj: &dyn Drawable) {
    obj.draw();
}

ภาพประกอบ: Trait vs Generic vs dyn

บทสรุป

  • Trait คือ interface ของ Rust
  • Generic ทำให้โค้ด reusable ได้
  • Polymorphism ช่วยให้โค้ด flexible รองรับหลาย struct ได้
  • ใช้ trait bound เพื่อจำกัดความสามารถที่รับได้
  • ใช้ dyn Trait เมื่อจำเป็นต้อง dispatch ตอน runtime

บทความโดย poolsawat.com – ชุมชน Rust และภาษาโปรแกรมยุคใหม่

Leave a Reply

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *