Keywords: collections, config, serde, ownership
อ่านแบบคน Python:
- ถ้าอยากเอา “ภาพรวม” ก่อน: คิดว่า
Vec/HashMap/structคือ “list/dict + schema ที่ชัด” - ถ้าอยาก “ลงมือทำ”: เริ่มจากตัวอย่างเล็ก ๆ แล้วค่อย tighten schema ทีละขั้น
- ถ้าติด: เปิด 12-learning-playbook.md
บทนี้โฟกัสการย้ายจาก list/dict ไปสู่ Vec/HashMap/struct และการ model JSON ให้ type ชัดขึ้น
ภาพที่อยากให้คุณจับให้ได้:
- Python เก็บข้อมูลแบบยืดหยุ่นมาก (list/dict ใส่อะไรก็ได้) แต่ความยืดหยุ่นนั้นมีต้นทุน: bug โผล่ตอน runtime
- Rust บังคับให้ “รูปทรงของข้อมูล” (shape) ชัดขึ้น ทำให้ compiler/IDE ช่วยได้จริง และ refactor ได้มั่นใจ
แนวคิดสำคัญ (เอาไปใช้ได้จริงในโปรเจกต์):
- รับข้อมูลแบบหลวมที่ boundary → แปลงเป็น typed struct ให้เร็วที่สุด
Vec<T> คือ list แบบ Rust ที่บอกชัดว่าเก็บ T ชนิดเดียวเท่านั้น
มุมมองคน Python:
- ถ้าคุณเคยเจอ list ที่ใส่ทั้ง string/int แล้วลืมเช็ค type ตอนใช้ → Rust ตัดปัญหานี้ด้วย type system
Python:
xs = [1, 2, 3]
xs.append(4)
print(xs)Output (example):
[1, 2, 3, 4]
Rust:
fn main() {
let mut xs = vec![1, 2, 3];
xs.push(4);
println!("{:?}", xs);
}Output (example):
[1, 2, 3, 4]
xs.iter()→ ยืมอ่าน (&T)xs.iter_mut()→ ยืมแก้ element ในที่เดิม (&mut T)xs.into_iter()→ ย้าย ownership (T) ออกมา (เหมาะกับการแปลง/consume)
หลักคิด: ถ้าคุณเลือกชนิดการยืมให้ตรงเจตนา borrow checker จะช่วยกันบั๊กให้
HashMap<K, V> คือ dict แบบ Rust: key/value มีชนิดชัดเจน
ข้อแตกต่างที่คนมาจาก Python ควรรู้ (แบบ practical):
- การอ่านค่าใน Rust มักได้
Option<&V>เพราะ key อาจไม่มีจริง ๆ → type system บังคับให้ handle เคส key หาย - การ “ถือ reference” ของค่าจาก map จะผูกกับการยืม map ทั้งก้อน ดังนั้นบางครั้งคุณต้องจัดลำดับโค้ดให้ชัดขึ้น
Python:
m = {"a": 1}
m["b"] = 2
print(m["a"])Output (example):
1
Rust:
use std::collections::HashMap;
fn main() {
let mut m: HashMap<String, i32> = HashMap::new();
m.insert("a".to_string(), 1);
m.insert("b".to_string(), 2);
if let Some(v) = m.get("a") {
println!("a={v}");
}
}Output (example):
a=1
ใน Python คุณอาจทำ:
m.setdefault(k, 0); m[k] += 1
ใน Rust นิยมใช้ entry เพราะมันทำงานใน style “ถ้ามีแล้วแก้ ถ้าไม่มีให้สร้าง” แบบปลอดภัยและชัดเจน
use std::collections::HashMap;
fn main() {
let mut counts: HashMap<String, u64> = HashMap::new();
let word = "hi".to_string();
*counts.entry(word).or_insert(0) += 1;
}Output (example):
(no output — snippet only)
นี่คือ “จุดเปลี่ยน” ที่ทำให้ Rust คุ้ม:
- struct ทำให้ compiler ตรวจ schema ให้
- IDE ช่วย autocomplete
- เวลา refactor ชื่อ field/ชนิดข้อมูล จะเจอ compile error ที่ชัด (ไม่หลุด runtime)
Skeleton:
struct Account {
name: String,
paused: bool,
}Output (example):
(no output — this snippet defines a struct)
เทียบกับ Python:
- ใน Python คุณมักเก็บข้อมูลหลายชนิดใน dict ซ้อน dict → Rust ทำเป็น struct ซ้อน struct ได้ เพื่อให้ schema ชัดและ IDE ช่วยได้
ทิป: ถ้าคุณยังไม่แน่ใจ schema
- เริ่มจาก
serde_json::Valueเพื่อ explore - แล้วค่อยย้าย field ที่ใช้จริง มาเป็น struct ทีละก้อน
ตัวอย่างแนวคิด: อ่านไฟล์ config JSON แล้วแปลงเป็น model ที่ type ชัด
Prereq (เพิ่ม dependencies ใน Cargo.toml):
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"แนวทางที่ใช้งานจริงบ่อย:
- เริ่มจาก
serde_json::Valueถ้ายังไม่รู้ schema - แล้วค่อยสร้าง
structทีละส่วน
แนวคิด: ใช้ raw string (r#"..."#) เพื่อไม่ต้อง escape quote (อ่านง่ายกว่า)
use serde_json::Value;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let text = r#"{"addrlists":["x"]}"#;
let v: Value = serde_json::from_str(text)?;
println!("{}", v["addrlists"][0]);
Ok(())
}Output (example):
x
ข้อควรระวัง (สำคัญ):
v["key"]ถ้า key หาย จะได้Null(ไม่ error) → ถ้าเผลอ default เงียบ ๆ bug จะหลุดง่าย
use serde::Deserialize;
#[derive(Deserialize)]
struct EndpointConfig {
addrlists: Vec<String>,
}Output (example):
(no output — this snippet defines a struct)
ข้อดีที่ได้ทันที:
- ถ้า
addrlistsเป็น type อื่น (เช่น string แทน list) →serde_jsonจะ error ให้ - ถ้า field หาย (และไม่ใช่
Option/ไม่ตั้ง default) → error ชัดว่า schema ไม่ตรง
อ้างอิง:
- ทำ
Vec<String>เก็บชื่อ 3 ชื่อ แล้ววนพิมพ์ - ทำ
HashMap<String, u64>เก็บ counter แล้วเพิ่มค่า (ลองใช้.entry(...).or_insert(0)) - เริ่มจาก JSON string → parse เป็น
Value→ แล้วเปลี่ยนเป็นstruct
ถ้าทำข้อ 3 แล้วอยากให้ “เข้มขึ้น”: ลองเพิ่ม #[serde(deny_unknown_fields)] (จะเจอใน 17-serde-advanced.md)