Skip to content

Latest commit

 

History

History
229 lines (167 loc) · 8.3 KB

File metadata and controls

229 lines (167 loc) · 8.3 KB

04 — Collections & Data Modeling (dict/list → HashMap/Vec/struct)

TOC · Prev · Next

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 ให้เร็วที่สุด

1) listVec<T>

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]

1.1 “ยืมอ่าน” vs “ยืมแก้” ใน Vec

  • xs.iter() → ยืมอ่าน (&T)
  • xs.iter_mut() → ยืมแก้ element ในที่เดิม (&mut T)
  • xs.into_iter() → ย้าย ownership (T) ออกมา (เหมาะกับการแปลง/consume)

หลักคิด: ถ้าคุณเลือกชนิดการยืมให้ตรงเจตนา borrow checker จะช่วยกันบั๊กให้


2) dictHashMap<K, V>

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

2.1 Pattern ที่ใช้บ่อย: entry (เทียบกับ setdefault)

ใน 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)

3) จาก dict หลวม ๆ → struct ชัด ๆ

นี่คือ “จุดเปลี่ยน” ที่ทำให้ 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 ทีละก้อน

4) JSON modeling ด้วย serde

ตัวอย่างแนวคิด: อ่านไฟล์ config JSON แล้วแปลงเป็น model ที่ type ชัด

Prereq (เพิ่ม dependencies ใน Cargo.toml):

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

แนวทางที่ใช้งานจริงบ่อย:

  • เริ่มจาก serde_json::Value ถ้ายังไม่รู้ schema
  • แล้วค่อยสร้าง struct ทีละส่วน

4.1 เริ่มจาก Value (หลวม แต่เร็วสำหรับ explore)

แนวคิด: ใช้ 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 จะหลุดง่าย

4.2 เปลี่ยนเป็น typed struct (tighten schema)

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 ไม่ตรง

อ้างอิง:


5) แบบฝึกหัด

  1. ทำ Vec<String> เก็บชื่อ 3 ชื่อ แล้ววนพิมพ์
  2. ทำ HashMap<String, u64> เก็บ counter แล้วเพิ่มค่า (ลองใช้ .entry(...).or_insert(0))
  3. เริ่มจาก JSON string → parse เป็น Value → แล้วเปลี่ยนเป็น struct

ถ้าทำข้อ 3 แล้วอยากให้ “เข้มขึ้น”: ลองเพิ่ม #[serde(deny_unknown_fields)] (จะเจอใน 17-serde-advanced.md)