Rust Option::zip: รวมค่าที่ต้องมาคู่กันโดยไม่ซ่อนสถานะขาดครึ่ง
ข้อมูลตั้งค่าหลายชุดมีค่าที่ต้องมาพร้อมกัน เช่นเวลาเริ่มกับเวลาจบ ถ้าเขียน if let ซ้อนกัน เราอาจเผลอทำให้กรณีมีเพียงฝั่งเดียวดูเหมือน “ไม่ตั้งค่า” Option::zip รวมสอง Option เป็น Some((a, b)) เฉพาะเมื่อทั้งคู่มีค่าครับ
WHY: บังคับคู่ก่อนตรวจความสัมพันธ์
เหมือนคูปองหมูกระทะที่ต้องมีทั้งบัตรโต๊ะและบัตรเวลา ขาดใบใดใบหนึ่งไม่ใช่ชุดใช้งานได้ แต่ “ขาดทั้งคู่” อาจหมายถึงใช้ค่า default เราจึงควรแยกสถานะขาดครึ่งออกจากช่วงที่ค่าครบแต่ลำดับผิด
#[derive(Debug, PartialEq)]
enum WindowError { Incomplete, Reversed }
fn window(start: Option<u32>, end: Option<u32>) -> Result<Option<(u32, u32)>, WindowError> {
match (start, end) {
(None, None) => Ok(None),
(Some(_), None) | (None, Some(_)) => Err(WindowError::Incomplete),
(start, end) => {
let (start, end) = start.zip(end).expect("both variants are Some");
if start > end { Err(WindowError::Reversed) } else { Ok(Some((start, end))) }
}
}
}
#[test]
fn preserves_missing_and_invalid_states() {
assert_eq!(window(None, None), Ok(None));
assert_eq!(window(Some(8), Some(17)), Ok(Some((8, 17))));
assert_eq!(window(Some(8), None), Err(WindowError::Incomplete));
assert_eq!(window(Some(18), Some(17)), Err(WindowError::Reversed));
}
HOW: zip ไม่ได้ validate ค่า
zip ทำหน้าที่แค่รวม presence มันไม่รู้ว่า start <= end หรือค่าตรง schema หรือไม่ จึงเหมาะกับขั้นแรกของ pipeline แล้วค่อยใช้ filter, and_then หรือ Result อธิบายเหตุผลที่ปฏิเสธ ตัวอย่างเลือก match ก่อนเพื่อรักษาความต่างระหว่าง “ไม่ตั้งทั้งคู่” กับ “ส่งมาไม่ครบ”
ถ้าธุรกิจยอมให้มีฝั่งเดียวจริง อย่าฝืน zip เพราะมันจะทิ้งค่านั้นเป็น None แบบเงียบ ๆ สร้าง enum เช่น Unbounded, From, Until, Between จะสื่อ state ได้ดีกว่า ส่วน zip_with ยังไม่ stable ในทุก toolchain ที่ใช้งานทั่วไป จึงไม่ควรผูกบทความกับ API ที่ยังต้อง feature gate
Recap
- ใช้
zipเมื่อสองค่า meaningful เฉพาะตอนมีพร้อมกัน - แยก absent-both, incomplete และ invalid-value ให้ชัด
- อย่าคิดว่า
zipตรวจ business rule ให้แล้ว - ถ้ามีสถานะมากกว่าสองแบบ ใช้ enum แทนคู่
Option - ทดสอบครบทั้ง four-way presence matrix