ข้ามไปยังเนื้อหา

Closures & Iterators

ใน TypeScript arrow functions สามารถจับตัวแปรจาก enclosing scope ได้อย่างอิสระ Rust closures ทำสิ่งเดียวกัน แต่ compiler ติดตามอย่างชัดเจนว่า closure ใช้ค่าที่จับไว้อย่างไร และบังคับ ownership rules ตอน compile time

TypeScript
// TypeScript — arrow function captures 'factor' by reference
const factor = 3;
const triple = (x: number) => x * factor;
console.log(triple(5)); // 15
// Closures in higher-order functions
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(x => x * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// Capturing mutable state — counter
let count = 0;
const increment = () => { count++; };
increment();
increment();
console.log(count); // 2
Rust
fn 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
}

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
}

ใน TypeScript async () => { ... } เป็นแค่ function ที่ return promise syntax ไม่ได้เปลี่ยนอะไรเลย ส่วนฝั่ง Rust เรื่องนี้เคยงุ่มง่ามอยู่หลายปี: closure ที่ return async { ... } จะได้ future ที่ borrow จาก capture ของ closure ไม่ได้ อะไรก็ตามที่ future แตะต้องจึงต้อง clone หรือ move ออกมาก่อน

Rust 1.85 เพิ่ม async closure ของจริง — async || { ... } — พร้อม trait AsyncFn, AsyncFnMut และ AsyncFnOnce ที่ล้อกับ Fn ทั้งสามตัวข้างบน future ที่มัน return borrow จาก capture ได้ นี่คือสิ่งที่ทำให้ code แบบธรรมชาติ compile ผ่าน

TypeScript
// TypeScript: async arrow function
const runTwice = async (f: (url: string) => Promise<number>) =>
(await f("https://a.example")) + (await f("https://bb.example"));
const log: string[] = [];
const total = await runTwice(async (url) => {
const n = await fetchLen(url);
log.push(`${url} -> ${n}`); // closes over 'log' freely
return n;
});
Rust
// Rust: async closure, bound with AsyncFnMut
async fn run_twice<F>(mut f: F) -> usize
where
F: AsyncFnMut(&str) -> usize,
{
f("https://a.example").await + f("https://bb.example").await
}
let mut log: Vec<String> = Vec::new();
let total = run_twice(async |url: &str| {
let n = fetch_len(url).await;
log.push(format!("{url} -> {n}")); // borrows 'log' across the await
n
})
.await;

เลือก bound ให้ตรงกับวิธีที่ closure ถูกใช้ เหมือนกับ Fn/FnMut/FnOnce เป๊ะ: AsyncFn ถ้าอ่าน capture อย่างเดียว, AsyncFnMut ถ้าแก้ไข capture (ตัวอย่างนี้ push ลง log), AsyncFnOnce ถ้าใช้ capture จนหมด

// @skip-verify crate: tokio
async fn fetch_len(url: &str) -> usize {
url.len() // pretend this awaits the network
}
// Takes an async closure: AsyncFnMut can mutate what it captures.
async fn run_twice<F>(mut f: F) -> usize
where
F: AsyncFnMut(&str) -> usize,
{
f("https://a.example").await + f("https://bb.example").await
}
#[tokio::main]
async fn main() {
let mut log: Vec<String> = Vec::new();
let total = run_twice(async |url: &str| {
let n = fetch_len(url).await;
log.push(format!("{url} -> {n}"));
n
})
.await;
println!("total = {total}");
for line in &log {
println!("{line}");
}
}

.map(), .filter(), และ .reduce() ของ TypeScript eager: จัดสรร array ใหม่ในแต่ละขั้น Iterator adapters ของ Rust lazy: ไม่มีการคำนวณจนกว่าคุณจะเรียก consuming method เช่น .collect(), .sum(), หรือ .for_each() หมายความว่า chain ยาวของ .map().filter().take() ไม่จัดสรร vectors ระหว่างกลางเลย

TypeScript
// TypeScript — each step allocates a new array
const 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);
Rust
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);
}
ควรใช้ `Fn` trait ใดเมื่อ closure แค่อ่าน (ไม่ mutate) ค่าที่จับไว้และสามารถเรียกได้หลายครั้ง?
Iterator chain ของ Rust คำนวณจริงเมื่อใด?
keyword `move` ทำอะไรเมื่อใช้ก่อน closure?
Iterator adapter ใดของ Rust ที่เทียบเท่ากับ `Array.prototype.flatMap` ของ JavaScript?