Skip to content

Latest commit

 

History

History
240 lines (171 loc) · 7.76 KB

File metadata and controls

240 lines (171 loc) · 7.76 KB

14 — Traits & Generics (จาก Duck Typing → Trait Bounds)

TOC · Prev · Next

Keywords: traits, generics, iterators, modules

อ่านแบบคน Python:

  • ถ้าอยากเอา “ภาพรวม” ก่อน: trait = สัญญาความสามารถ (คล้าย protocol/interface)
  • ถ้าอยาก “ลงมือทำ”: เริ่มจาก trait เล็ก ๆ 1 ตัว แล้วค่อยเพิ่ม T: Trait ทีละจุด
  • ถ้าติด: เปิด 12-learning-playbook.md

บทนี้เชื่อม “ความเคยชินแบบ Python” (duck typing / protocol-ish) ไปสู่ Rust: trait, impl, generics, และ trait bounds ให้พอใช้ทำงานจริง โดยไม่ต้องลงทฤษฎีหนัก

แก่นของบทนี้:

  • ใน Rust “ความสามารถ” ถูกประกาศชัดด้วย trait
  • generic ทำให้โค้ด reuse ได้โดยยังคง type-safety
  • เลือก static dispatch (generic) vs dynamic dispatch (dyn Trait) ให้เหมาะกับงาน

แนวคิด:

  • static dispatch (generic) = compiler รู้ชนิดตอน compile → เร็วและตรง
  • dynamic dispatch (dyn Trait) = เลือก method ตอน runtime → ยืดหยุ่นเวลาเก็บหลายชนิด

1) Python mindset: “ถ้ามันมีเมธอดนี้ ก็ใช้ได้”

ใน Python คุณอาจเขียนฟังก์ชันรับอะไรก็ได้ ขอแค่มี .describe():

def print_desc(v) -> None:
    print(v.describe())

Rust ต้องการความชัด: type ไหน “สัญญา” ว่ามีความสามารถนั้น


2) Trait คือ “สัญญาความสามารถ”

ตัวอย่าง trait ง่าย ๆ:

trait Describe {
    fn describe(&self) -> String;
}

struct User {
    name: String,
}

impl Describe for User {
    fn describe(&self) -> String {
        format!("User(name={})", self.name)
    }
}

Output (example):

(no output — trait/type/impl definition only)

มุมมองคน Python:

  • trait ~ interface/protocol
  • impl Trait for Type ~ type นี้ทำตามสัญญาแล้ว

3) เรียกใช้ trait: generic vs dyn Trait

คำถามที่ต้องตอบก่อนเลือก: “เราต้องเก็บหลาย type รวมกันไหม?”

A) Generic (static dispatch): เร็ว + type ชัด

fn print_desc<T: Describe>(v: &T) {
    println!("{}", v.describe());
}

Output (example):

(no output — function definition only)

แนวคิด:

  • compiler จะ specialize ฟังก์ชันนี้ให้กับ T แต่ละตัวที่ใช้จริง

B) Trait object (dynamic dispatch): ยืดหยุ่นเวลาเก็บหลายชนิด

fn print_desc_dyn(v: &dyn Describe) {
    println!("{}", v.describe());
}

Output (example):

(no output — function definition only)

ใช้เมื่อ:

  • คุณต้องเก็บค่าหลายชนิดไว้ใน collection เดียวกัน
  • หรือ API ต้องรับ “อะไรก็ได้ที่ทำ Describe” แบบ runtime

ตัวอย่าง collection ที่รวมหลาย type ด้วย Box<dyn Describe>:

fn demo(items: Vec<Box<dyn Describe>>) {
    for v in items {
        println!("{}", v.describe());
    }
}

Output (example):

(no output — function definition only)

4) impl Trait ใน argument/return (syntax ที่เจอบ่อย)

4.1 ใน argument: เป็น syntax sugar ของ generic

fn print_any(v: impl Describe) {
    println!("{}", v.describe());
}

Output (example):

(no output — function definition only)

4.2 ใน return: คืน “type เดียว” แต่ไม่เปิดชื่อ type

fn make_user(name: &str) -> impl Describe {
    User { name: name.to_string() }
}

Output (example):

(no output — function definition only)

ข้อจำกัดที่ต้องจำ:

  • -> impl Describe ต้องคืน type เดียวกันเสมอ
  • ถ้าต้องคืนหลายแบบ ให้ใช้ enum หรือ Box<dyn Describe>

5) Trait bounds มากกว่า 1 อัน (และ where clause)

บางครั้งคุณต้องการหลายสัญญา:

fn dump<T: Describe + std::fmt::Debug>(v: &T) {
    println!("{:?} | {}", v, v.describe());
}

Output (example):

(no output — function definition only)

ถ้ายาว ให้ใช้ where จะอ่านง่ายกว่า:

fn dump2<T>(v: &T)
where
    T: Describe + std::fmt::Debug,
{
    println!("{:?} | {}", v, v.describe());
}

Output (example):

(no output — function definition only)

6) จาก Python “ของยืดหยุ่น” → Rust “ต้องเลือกโมเดลข้อมูล”

ใน Python list/dict เก็บอะไรก็ได้ แต่คุณอาจพังตอน runtime

ใน Rust:

  • Vec<T> เก็บได้เฉพาะ T
  • ถ้าต้องการหลายชนิด: ใช้ enum หรือ Box<dyn Trait>

ตัวอย่าง enum ที่ปลอดภัยและ match ได้ครบ:

enum Value {
    Int(i64),
    Text(String),
}

Output (example):

(no output — type definition only)

แนวทางสำหรับคนมาจาก Python:

  • ถ้า “จำนวนชนิดจำกัดและรู้ล่วงหน้า” → enum
  • ถ้า “อยากให้ plugin/extensible” → trait + dyn Trait

7) Pitfalls ที่เจอบ่อย (แล้วควรคิดยังไง)

  1. อยากทำ “object ที่มี field dynamic”
  • ใน Rust มักใช้ enum + match หรือใช้ serde_json::Value ชั่วคราวแล้วค่อย tighten (บท 09/17)
  1. อยากคืนหลาย type จากฟังก์ชันเดียว
  • ใช้ enum หรือ trait object (Box<dyn Trait>)
  1. พยายามใช้ trait object แล้วติดเรื่อง ownership
  • จำว่า dyn Trait มักต้องอยู่หลัง reference (&dyn Trait) หรือ pointer (Box<dyn Trait>)

8) แบบฝึกหัด (Exercises)

  1. สร้าง trait ToLine ที่มี fn to_line(&self) -> String
  • ทำ impl ToLine สำหรับ i32 และ String
  1. เขียน fn join_lines<T: ToLine>(items: &[T]) -> String ที่ join แต่ละบรรทัดด้วย \n

  2. สร้าง enum Item { Num(i32), Text(String) } แล้วทำ impl ToLine for Item

  3. (ท้าทาย) ทำ Vec<Box<dyn ToLine>> ใส่ทั้ง i32 และ String แล้วพิมพ์ออกมาทุกบรรทัด