Rust map_while: แปลงข้อมูลจนเจอเส้นแบ่ง แล้วหยุดอย่างมีขอบเขต
ข้อมูลแบบ header, log หรือคำสั่งมักมีส่วนต้นที่อ่านได้ต่อเนื่อง แล้วจบเมื่อเจอเส้นว่างหรือ token ที่ไม่ตรงรูปแบบ การเขียน map(...).take_while(...) ต้องแปลงก่อนแล้วค่อยตรวจ และมักเหลือ Option ซ้อนกัน Iterator::map_while รวมการแปลงกับเงื่อนไขหยุดไว้ใน closure เดียวครับ
closure คืน Some(value) เพื่อส่งค่าต่อ หรือ None เพื่อหยุด เหมือนเจ้าหน้าที่สายพานที่คัดของใส่กล่องไปเรื่อย ๆ จนเห็นแผ่นกั้น แล้วหยุดทั้งสายโดยไม่ส่งแผ่นกั้นเข้าไปในกล่องผลลัพธ์
fn numeric_prefix<'a>(lines: impl IntoIterator<Item = &'a str>) -> Vec<u32> {
lines
.into_iter()
.map_while(|line| line.strip_prefix("id=")?.parse().ok())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stops_at_first_non_matching_line() {
let lines = ["id=7", "id=11", "--", "id=99"];
assert_eq!(numeric_prefix(lines), vec![7, 11]);
}
#[test]
fn parse_error_is_a_boundary_too() {
let lines = ["id=3", "id=nope", "id=8"];
assert_eq!(numeric_prefix(lines), vec![3]);
}
}
รายละเอียดสำคัญคือ item ที่ทำให้ closure คืน None ถูกดึงออกจาก iterator แล้ว หากต้องเก็บ delimiter ไว้ให้ parser ขั้นถัดไป ควรใช้ peekable() แล้วตรวจด้วย peek ก่อน consume นอกจากนี้เอกสารระบุว่า iterator จาก map_while ไม่รับประกันว่าจะคืน None ตลอดไปหลังหยุด ถ้าต้องเรียกต่อหลายครั้งให้เติม .fuse() หรือออกแบบให้ consume ครั้งเดียว
ถ้าโจทย์คือหยุดตาม predicate โดยไม่แปลง ใช้ take_while จะอ่าน intent ตรงกว่า ถ้าทุก item ต้องแปลงและ error ต้องส่งกลับผู้เรียก ใช้ map แล้ว collect::<Result<Vec<_>, _>>() เพื่อไม่ทำ error กลายเป็นเพียงจุดจบเงียบ ๆ
Recap
- ใช้
map_whileเมื่อการแปลงและจุดหยุดเป็นเงื่อนไขเดียวกัน - ถือว่า item ที่คืน
Noneถูก consume ไปแล้ว - อย่ากลืน error ที่ผู้เรียกจำเป็นต้องรู้
- ใช้
peekableเมื่อ delimiter ต้องส่งต่อให้ parser ถัดไป