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 → ยืดหยุ่นเวลาเก็บหลายชนิด
ใน Python คุณอาจเขียนฟังก์ชันรับอะไรก็ได้ ขอแค่มี .describe():
def print_desc(v) -> None:
print(v.describe())Rust ต้องการความชัด: type ไหน “สัญญา” ว่ามีความสามารถนั้น
ตัวอย่าง 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 นี้ทำตามสัญญาแล้ว
คำถามที่ต้องตอบก่อนเลือก: “เราต้องเก็บหลาย type รวมกันไหม?”
fn print_desc<T: Describe>(v: &T) {
println!("{}", v.describe());
}Output (example):
(no output — function definition only)
แนวคิด:
- compiler จะ specialize ฟังก์ชันนี้ให้กับ
Tแต่ละตัวที่ใช้จริง
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)
fn print_any(v: impl Describe) {
println!("{}", v.describe());
}Output (example):
(no output — function definition only)
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>
บางครั้งคุณต้องการหลายสัญญา:
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)
ใน 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
- อยากทำ “object ที่มี field dynamic”
- ใน Rust มักใช้
enum + matchหรือใช้serde_json::Valueชั่วคราวแล้วค่อย tighten (บท 09/17)
- อยากคืนหลาย type จากฟังก์ชันเดียว
- ใช้
enumหรือ trait object (Box<dyn Trait>)
- พยายามใช้ trait object แล้วติดเรื่อง ownership
- จำว่า
dyn Traitมักต้องอยู่หลัง reference (&dyn Trait) หรือ pointer (Box<dyn Trait>)
- สร้าง trait
ToLineที่มีfn to_line(&self) -> String
- ทำ
impl ToLineสำหรับi32และString
-
เขียน
fn join_lines<T: ToLine>(items: &[T]) -> Stringที่ join แต่ละบรรทัดด้วย\n -
สร้าง
enum Item { Num(i32), Text(String) }แล้วทำimpl ToLine for Item -
(ท้าทาย) ทำ
Vec<Box<dyn ToLine>>ใส่ทั้งi32และStringแล้วพิมพ์ออกมาทุกบรรทัด