Closures & Iterators
TypeScript arrow functions vs Rust closures
หัวข้อที่มีชื่อว่า “TypeScript arrow functions vs Rust closures”ใน TypeScript arrow functions สามารถจับตัวแปรจาก enclosing scope ได้อย่างอิสระ Rust closures ทำสิ่งเดียวกัน แต่ compiler ติดตามอย่างชัดเจนว่า closure ใช้ค่าที่จับไว้อย่างไร และบังคับ ownership rules ตอน compile time
// TypeScript — arrow function captures 'factor' by referenceconst factor = 3;const triple = (x: number) => x * factor;console.log(triple(5)); // 15
// Closures in higher-order functionsconst numbers = [1, 2, 3, 4, 5];const doubled = numbers.map(x => x * 2);console.log(doubled); // [2, 4, 6, 8, 10]
// Capturing mutable state — counterlet count = 0;const increment = () => { count++; };increment();increment();console.log(count); // 2fn main() { // Closure captures 'factor' by reference (immutable) let factor = 3; let triple = |x| x * factor; println!("{}", triple(5)); // 15
// Iterator adapters: map, filter, collect let numbers = vec![1, 2, 3, 4, 5]; let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect(); println!("{:?}", doubled); // [2, 4, 6, 8, 10]
// FnMut closure — captures count mutably let mut count = 0; let mut increment = || { count += 1; }; increment(); increment(); drop(increment); // release borrow so we can use count again println!("{count}"); // 2}ลำดับชั้นของ Fn traits
หัวข้อที่มีชื่อว่า “ลำดับชั้นของ Fn traits”Rust จำลองความสามารถของ closure เป็นสาม traits การเข้าใจสิ่งเหล่านี้มีความสำคัญเมื่อคุณส่ง closures ไปยังฟังก์ชันหรือเก็บไว้ใน structs
| Trait | ทำอะไรได้ | คู่เทียบ TypeScript |
|---|---|---|
FnOnce | เรียกได้ครั้งเดียว; สามารถ move ค่าที่จับไว้ออกได้ | One-shot callback ที่ consume ค่าที่จับไว้ |
FnMut | เรียกได้หลายครั้ง; เปลี่ยนแปลงค่าที่จับไว้ | Stateful callback ที่แก้ไขตัวแปรที่ close over |
Fn | เรียกได้หลายครั้ง; อ่านค่าที่จับไว้เท่านั้น (หรือไม่จับเลย) | Pure arrow function ไม่มี side effects บนตัวแปรที่จับ |
ทุก Fn เป็น FnMut ด้วย; ทุก FnMut เป็น FnOnce ด้วย เมื่อคุณรับ parameter &dyn Fn(T) -> U คุณยอมรับ requirement ที่เข้มงวดที่สุด — closure ต้องเรียกได้หลายครั้งโดยไม่ mutate
fn apply_twice<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(f(x))}
fn main() { let double = |x| x * 2; println!("{}", apply_twice(double, 3)); // 12}Iterator adapters — lazy โดย default
หัวข้อที่มีชื่อว่า “Iterator adapters — lazy โดย default”.map(), .filter(), และ .reduce() ของ TypeScript eager: จัดสรร array ใหม่ในแต่ละขั้น Iterator adapters ของ Rust lazy: ไม่มีการคำนวณจนกว่าคุณจะเรียก consuming method เช่น .collect(), .sum(), หรือ .for_each() หมายความว่า chain ยาวของ .map().filter().take() ไม่จัดสรร vectors ระหว่างกลางเลย
// TypeScript — each step allocates a new arrayconst result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(x => x % 2 === 0) // new array [2,4,6,8,10] .map(x => x * x) // new array [4,16,36,64,100] .slice(0, 3); // new array [4,16,36]console.log(result);fn main() { // Rust — no intermediate allocations until collect() let result: Vec<i32> = (1..=10) .filter(|x| x % 2 == 0) // lazy .map(|x| x * x) // lazy .take(3) // lazy .collect(); // ONE allocation here println!("{:?}", result); // [4, 16, 36]
// Common adapters let sum: i32 = (1..=100).sum(); println!("sum 1..100 = {sum}");
let words = vec!["hello", "world", "rust"]; let upper: Vec<String> = words.iter() .map(|w| w.to_uppercase()) .collect(); println!("{:?}", upper);
// flat_map (like Array.flatMap) let sentences = vec!["hello world", "foo bar"]; let all_words: Vec<&str> = sentences.iter() .flat_map(|s| s.split_whitespace()) .collect(); println!("{:?}", all_words);}ลองเล่นดู
หัวข้อที่มีชื่อว่า “ลองเล่นดู”fn main() { let multiplier = 3; let triple = |x| x * multiplier; println!("triple(5) = {}", triple(5));
let numbers = vec![1, 2, 3, 4, 5, 6]; let evens_doubled: Vec<i32> = numbers .iter() .filter(|&&x| x % 2 == 0) .map(|&x| x * 2) .collect(); println!("evens_doubled = {:?}", evens_doubled);
let sum: i32 = numbers.iter().sum(); println!("sum = {sum}");
let result: Vec<String> = (1..=3) .map(|x| x * x) .map(|x| format!("sq:{x}")) .collect(); println!("{:?}", result);}Compiling…