Rust Option::take_if: ย้ายค่าออกเมื่อเงื่อนไขผ่าน โดยไม่ชน borrow
โจทย์เล็ก ๆ ที่เจอบ่อยคือ “ถ้าค่าข้างใน Option พร้อมแล้ว ให้ย้ายออกไปทำงาน; ถ้ายังไม่พร้อม ให้คงไว้” หากแยกตรวจด้วย as_ref() แล้วค่อย take() เราต้องรักษาเงื่อนไขสองช่วงให้ตรงกันเอง Option::take_if รวมการตรวจและการย้ายไว้ใน operation เดียวครับ
นึกถึงตู้รับอาหาร: พนักงานเปิดดูถุง ปรับป้ายสถานะได้ และหยิบออกเฉพาะเมื่อครบรายการ ถ้ายังไม่ครบ ถุงเดิมยังอยู่ในตู้ ไม่ต้องวางคืนด้วยมือ
#[derive(Debug, PartialEq)]
struct Job {
attempts: u8,
ready: bool,
}
fn claim_ready(slot: &mut Option<Job>) -> Option<Job> {
slot.take_if(|job| {
job.attempts += 1;
job.ready && job.attempts <= 3
})
}
fn main() {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn moves_only_a_ready_job() {
let mut ready = Some(Job { attempts: 0, ready: true });
assert_eq!(claim_ready(&mut ready).unwrap().attempts, 1);
assert_eq!(ready, None);
let mut waiting = Some(Job { attempts: 0, ready: false });
assert_eq!(claim_ready(&mut waiting), None);
assert_eq!(waiting.unwrap().attempts, 1);
}
}
closure ได้ &mut T จึงแก้ค่าก่อนตัดสินใจได้ จุดนี้มีพลังแต่ต้องใช้ด้วยความตั้งใจ: แม้ predicate คืน false การแก้ไขที่ทำไปแล้ว ยังอยู่ อย่างในตัวอย่าง attempts เพิ่มเป็น 1 หากต้องการตรวจอย่างเดียว อย่า mutate หรือใช้ as_ref().is_some_and(...) สำหรับ boolean
take() เหมาะเมื่อย้ายออกแน่นอน, replace() เหมาะเมื่อสลับค่าใหม่เข้าไป, ส่วน take_if() เหมาะเมื่อเงื่อนไขขึ้นกับค่าปัจจุบันและต้องการคงค่าไว้เมื่อไม่ผ่าน
Recap
- ใช้
take_ifเมื่อ “ตรวจแล้วอาจย้ายออก” เป็นกติกาเดียวกัน - จำว่า predicate แก้ค่าภายในได้แม้คืน false
- ใช้
takeหากไม่มีเงื่อนไข - เขียน test ทั้งทางย้ายออกและทางคงค่า