Skip to content

Latest commit

 

History

History
223 lines (159 loc) · 6.86 KB

File metadata and controls

223 lines (159 loc) · 6.86 KB

15 — Iterators & Closures (map/filter/collect แบบ Rust)

TOC · Prev · Next

Keywords: iterators, closures, borrowing, tooling

อ่านแบบคน Python:

  • ถ้าอยากเอา “ภาพรวม” ก่อน: iterator chain = list comprehension + collect() ตอนท้าย
  • ถ้าอยาก “ลงมือทำ”: เลือกให้ชัดว่าใช้ iter (อ่าน) / iter_mut (แก้) / into_iter (ย้าย)
  • ถ้าติด: เปิด 12-learning-playbook.md

บทนี้สอน iterator/closure แบบที่คนมาจาก Python จะ “ติดมือ” เร็ว: iter()/iter_mut()/into_iter(), map/filter, collect, และ move closure (จุดที่มักชนตอนเริ่มเขียน Rust)

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

  • iterator chain = list comprehension แบบ Rust
  • เลือก iter/iter_mut/into_iter ให้ตรงเจตนา (อ่าน/แก้/ย้าย)
  • collect ต้องรู้ “ปลายทางเป็นอะไร”

1) เทียบกับ Python: list comprehension vs iterator chain

Python:

[f(x) for x in xs if cond(x)]

Rust:

xs.iter().filter(...).map(...).collect()

สังเกตความต่างเชิงนิสัย:

  • Python มัก “สร้าง list ใหม่ทันที”
  • Rust iterator chain มัก “ค่อย ๆ ประมวลผล” แล้ว collect() ทีเดียวตอนสุดท้าย

2) 3 ตัวที่ต้องจำ: iter() / iter_mut() / into_iter()

สมมติ let mut v: Vec<String> = ...;

  • v.iter() → ได้ &String (ยืม, อ่าน)
  • v.iter_mut() → ได้ &mut String (ยืมแบบแก้ได้)
  • v.into_iter() → ได้ String (ย้าย ownership ออกมา)

แนวทาง (เลือกให้ตรงเจตนา):

  • อ่านอย่างเดียว → iter()
  • แก้ element ในที่เดิม → iter_mut()
  • consume vector แล้วแปลงเป็นอย่างอื่น → into_iter()

3) map, filter, collect

ตัวอย่าง: เลือกคำที่ยาว > 3 แล้วทำเป็นตัวพิมพ์ใหญ่

Python:

words = ["hi", "rust", "from", "python"]
out = [w.upper() for w in words if len(w) > 3]
print(out)

Output (example):

['RUST', 'FROM', 'PYTHON']

Rust:

fn main() {
    let words = vec!["hi", "rust", "from", "python"];

    let out: Vec<String> = words
        .iter()
        .filter(|w| w.len() > 3)
        .map(|w| w.to_uppercase())
        .collect();

    println!("{:?}", out);
}

Output (example):

["RUST", "FROM", "PYTHON"]

3.1 ทำไมบางทีเห็น &&str

ถ้า words: Vec<&str> แล้วคุณเรียก .iter() คุณจะได้ &&str

ช่วงแรกถ้ารู้สึกว่ามันงง ใช้ pattern unpack ใน closure ได้:

let out: Vec<String> = words
    .iter()
    .filter(|&&w| w.len() > 3)
    .map(|&w| w.to_uppercase())
    .collect();

Output (example):

(no output — expression snippet only)

พอคุ้นแล้ว คุณจะเริ่มชินกับ deref coercion และไม่ต้องเขียน |&w| บ่อย


4) for loop ยังโอเค (ไม่ต้องฝืน functional)

Rust ไม่ได้บังคับให้ chain เสมอไป

let mut out = Vec::new();
for w in words.iter() {
    if w.len() > 3 {
        out.push(w.to_uppercase());
    }
}

Output (example):

(no output — snippet only)

แนวทาง:

  • ถ้ากำลังเรียน ให้ใช้แบบที่อ่านง่ายที่สุดก่อน
  • พอ logic ชัดแล้วค่อย refactor เป็น iterator chain เพื่อให้สั้น/ชัด

5) Closure จับตัวแปรจากข้างนอก (capture)

Python closure มัก “จับ reference” แบบ implicit

Rust closure จะ capture ตามที่จำเป็น:

  • ยืมแบบ &
  • ยืมแบบ &mut
  • ย้าย ownership (move)

ตัวอย่าง: เก็บ prefix ไว้ใช้ใน map (ไม่ต้อง move)

let prefix = String::from("item:");
let out: Vec<String> = words
    .iter()
    .map(|w| format!("{}{}", &prefix, w))
    .collect();

Output (example):

(no output — snippet only)

ถ้าจะส่งไป thread/spawn มักต้อง move:

let prefix = String::from("item:");
let handle = std::thread::spawn(move || {
    println!("{}", prefix);
});
handle.join().unwrap();

Output (example):

item:

จำง่าย:

  • move = ย้าย ownership เข้า closure เพื่อให้ closure อยู่ได้โดยไม่พึ่ง scope เดิม

6) collect ต้องรู้ type ปลายทาง

บางครั้ง Rust เดา type ไม่ได้ ต้องช่วยบอก:

let nums = vec!["1", "2", "3"];
let out: Vec<i32> = nums.iter().map(|s| s.parse::<i32>().unwrap()).collect();

Output (example):

(no output — snippet only)

ถ้าไม่อยากใช้ unwrap() ให้ดูบท 03-error-handling.md แล้วลองทำเป็น:

  • Result<Vec<i32>, _>

ทิปที่คนมาจาก Python ควรลองสักครั้ง:

let out: Result<Vec<i32>, _> = nums.iter().map(|s| s.parse::<i32>()).collect();

Output (example):

(no output — snippet only)

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

  1. เขียน fn even_squares(nums: &[i32]) -> Vec<i32>
  • เลือกเลขคู่ แล้วยกกำลังสอง
  1. เขียน fn parse_all(nums: &[&str]) -> Result<Vec<i32>, std::num::ParseIntError>
  • ใช้ map + collect::<Result<Vec<_>, _>>()
  1. ทำ Vec<String> แล้วใช้ iter_mut() เพื่อเติม suffix ให้ทุกตัว เช่น "!"

  2. (ท้าทาย) ทำ HashMap<String, i32> แล้วใช้ iterator เพื่อคัดเฉพาะค่ามากกว่า 10 ออกมาเป็น Vec<String> ของ key