😸 Dog, Cat, Bird เรียกใช้งานผ่านฟังก์ชัน print_speak(item: &dyn Speaker)

นี่คือตัวอย่างโค้ดแบบเต็มที่นำ Dog, Cat, และ Bird มาสร้างและเรียกใช้งานผ่านฟังก์ชัน print_speak(item: &dyn Speaker) ครับ คุณสามารถก๊อปปี้โค้ดนี้ไปรันในโปรเจกต์ Rust หรือใน Rust Playground ได้เลยครับ

// 1. นิยาม Trait ขึ้นมา
trait Speaker {
    fn speak(&self);
}

// 2. สร้าง Type (Struct) ทั้ง 3 ตัว
struct Dog;
struct Cat;
struct Bird;

// 3. Implement Trait ให้กับแต่ละ Type
impl Speaker for Dog {
    fn speak(&self) {
        println!("🐶 โฮ่ง ๆ!");
    }
}

impl Speaker for Cat {
    fn speak(&self) {
        println!("🐱 เหมียว ๆ!");
    }
}

impl Speaker for Bird {
    fn speak(&self) {
        println!("🐦 จิ๊บ ๆ!");
    }
}

// 4. ฟังก์ชันที่ใช้ `dyn Trait` (Dynamic Dispatch)
fn print_speak(item: &dyn Speaker) {
    // ตอนรันโปรแกรมจะมาเปิดตาราง vtable ที่นี่เพื่อดูว่าต้องเรียกฟังก์ชันของตัวไหน
    item.speak(); 
}

fn main() {
    // สร้าง Instance ของสัตว์แต่ละตัว
    let my_dog = Dog;
    let my_cat = Cat;
    let my_bird = Bird;

    println!("--- เรียกใช้งานทีละตัวผ่านฟังก์ชัน ---");
    // ส่ง Reference (&) เข้าไป ตัวแปรจะถูกแปลงเป็น Fat Pointer อัตโนมัติ
    print_speak(&my_dog);
    print_speak(&my_cat);
    print_speak(&my_bird);

    println!("\n--- ประโยชน์ของ dyn: เอามาใส่ Vec รวมกันได้ด้วย ---");
    // ด้วยพลังของ `dyn` เราสามารถเอาคนละ Type มาเก็บใน Vec เดียวกันได้ (Heterogeneous Collection)
    let zoo: Vec<&dyn Speaker> = vec![&my_dog, &my_cat, &my_bird];

    for animal in zoo {
        print_speak(animal);
    }
}

💡 อธิบายจุดสำคัญในโค้ด

  1. การส่งค่า: ตอนเรียกใช้ print_speak(&my_dog) เราต้องใส่เครื่องหมาย & เสมอ เพราะ dyn Speaker ไม่รู้ขนาดที่แน่นอนในหน่วยความจำ (Unsized) จึงต้องส่งผ่าน Pointer หรือ Reference เสมอ
  2. ความมหัศจรรย์ในฟังก์ชัน main ส่วนล่าง: สังเกตตรง Vec<&dyn Speaker> โค้ดนี้จะทำไม่ได้เลยถ้าเปลี่ยนฟังก์ชันเป็นแบบไม่ใช้ dyn (impl Speaker) เพราะคอมไพเลอร์ Rust จะไม่อนุญาตให้เอา Dog, Cat, Bird ซึ่งเป็นคนละโครงสร้างข้อมูลกันมาอยู่ใน Vec เดียวกันครับ
    จากโค้ดตัวอย่างนี้ มีการใช้ &dyn ซึ่งเป็นการยืมข้อมูลชั่วคราว (Borrowing) คุณอยากรู้เพิ่มเติมเกี่ยวกับวิธีใช้ Box<dyn Speaker> ในกรณีที่เราต้องการย้ายเจ้าของข้อมูล (Ownership) หรือต้องการสร้างสัตว์ขึ้นมาในฟังก์ชันแล้วส่งกลับออกมาไหมครับ?