ปูสีส้มแดงต้นฉบับใช้ก้ามสองข้างเปิดลิ้นชักแผนที่คนละช่อง โดยมีเส้นเตือนระหว่างช่องที่ห้ามซ้ำ

Rust HashMap::get_disjoint_mut: แก้หลายค่าใน Map โดยไม่ชนกฎ Borrow

พี่โชว์Engineering

สมมติระบบคลังสินค้าต้องย้ายจำนวนสินค้าจากสาขาหนึ่งไปอีกสาขาหนึ่ง ค่าอยู่ใน HashMap เดียวกัน แต่เราอยากแก้สองช่องพร้อมกัน การเรียก get_mut ครั้งแรกทำให้ map ถูกยืมอยู่ การเรียกครั้งที่สองจึงชน borrow checker แม้เรารู้ว่าคนละ key ก็ตาม

get_disjoint_mut เป็นทางบอกความตั้งใจนี้ตรง ๆ: ส่ง key หลายตัวใน array แล้วรับ array ของ Option<&mut V> กลับมา standard library ตรวจว่าไม่มี key ซ้ำก่อนคืน mutable references จึงไม่ต้องใช้ unsafe หรือถอดค่าออกแล้วใส่กลับครับ

ตัวอย่างด้านล่างเป็น fragment สำหรับอ่านแนวคิด (API ต้องใช้ Rust 1.86+; ชุด verify บนกล่องอาจเป็นรุ่นเก่ากว่า)

use std::collections::HashMap;

fn transfer(stock: &mut HashMap<&'static str, i32>, amount: i32) {
    let [north, south] = stock.get_disjoint_mut([&"north", &"south"]);
    let north = north.expect("north branch");
    let south = south.expect("south branch");
    *north -= amount;
    *south += amount;
}

fn main() {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn updates_two_distinct_entries() {
        let mut stock = HashMap::from([("north", 10), ("south", 4)]);
        transfer(&mut stock, 3);
        assert_eq!(stock["north"], 7);
        assert_eq!(stock["south"], 7);
    }
}

ขอบที่ต้องคิดก่อนใช้

key ที่ไม่มีอยู่ให้ None เฉพาะตำแหน่งนั้น เราจึงเลือกได้ว่าจะ fail ทั้ง transaction หรือข้ามบางช่อง ส่วน key ซ้ำเป็นความผิดของ caller เพราะจะทำให้ mutable reference สองตัวชี้ค่าเดียวกัน implementation จึงตรวจ overlap และ panic แทนการสร้าง alias ที่ไม่ปลอดภัย

การตรวจความซ้ำของ API แบบปลอดภัยมีต้นทุนตามจำนวน key ยกกำลังสอง จึงเหมาะกับชุดเล็กคงที่ เช่น สองถึงสี่รายการ ไม่ใช่ batch หลายพัน key ถ้าชุดใหญ่ ให้พิจารณาจัดกลุ่มข้อมูลใหม่, แก้ทีละรายการ หรือใช้ entry API ตาม semantics ของงาน

ข้อดีเชิงออกแบบคือชนิดคืนค่ายังรักษาความสัมพันธ์กับลำดับ key ทำให้ review ง่ายกว่า loop ที่เก็บ raw pointer แต่ต้องจัดการ partial missing ให้ชัด อย่า unwrap ถ้าข้อมูลจากผู้ใช้กำหนด key และอย่าจับ panic จาก duplicate เพื่อใช้เป็น control flow

Recap

  • ใช้เมื่ออยากแก้หลาย key ที่รับรองว่าไม่ทับกัน
  • จัดการ None ของ key ที่หายตามกฎธุรกิจ
  • ห้ามส่ง key ซ้ำ และอย่าใช้กับ batch ใหญ่มากโดยไม่วัด
  • ได้ความปลอดภัยของ borrow checker โดยไม่แตะ unsafe ครับ