ปูต้นฉบับตรวจลูกโอ๊กในกล่องตัวเลือกและคีบออกเฉพาะเมื่อผ่านเกณฑ์บนฉากสว่าง

Rust Option::take_if: ย้ายค่าออกเมื่อเงื่อนไขผ่าน โดยไม่ชน borrow

พี่โชว์Engineering

โจทย์เล็ก ๆ ที่เจอบ่อยคือ “ถ้าค่าข้างใน 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 ทั้งทางย้ายออกและทางคงค่า