Skip to content

Commit 7d717da

Browse files
committed
feat: add repair_handles helper for removal
1 parent e2e0873 commit 7d717da

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

src/lib.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ where
329329
/// my_handle -= 1;
330330
/// }
331331
/// ```
332+
/// See [`repair_handles`](Self::repair_handles) for a helper that automates this.
332333
pub fn remove<Q>(&mut self, item: &Q) -> Option<(H, T)>
333334
where
334335
T: Borrow<Q>,
@@ -360,11 +361,56 @@ where
360361
///
361362
/// Any existing handle `h` where `h > handle` must be decremented by 1 to
362363
/// remain valid.
364+
/// See [`repair_handles`](Self::repair_handles) for a helper that automates this.
363365
pub fn remove_handle(&mut self, handle: H) -> Option<T> {
364366
let idx = usize::try_from(handle).ok()?;
365367
self.items.shift_remove_index(idx)
366368
}
367369

370+
/// A helper to update a collection of handles after a removal.
371+
///
372+
/// When you call `remove`, handles greater than the removed index become invalid.
373+
/// This helper iterates over your collection of handles and decrements those
374+
/// that need to shift down, restoring their validity.
375+
///
376+
/// # Generic Support
377+
///
378+
/// This accepts any iterator that yields `&mut H`. This means it works with:
379+
/// - `&mut [H]` (slices and vectors of handles)
380+
/// - `.iter_mut()` on custom collections
381+
/// - `.iter_mut().map(|item| &mut item.id)` for structs containing handles
382+
///
383+
/// # Example
384+
///
385+
/// ```ignore
386+
/// let (removed_h, _) = interner.remove("ItemB").unwrap();
387+
///
388+
/// // Fix a simple vector of handles
389+
/// interner.repair_handles(removed_h, &mut my_handle_vec);
390+
///
391+
/// // Fix handles inside a custom struct
392+
/// interner.repair_handles(removed_h, my_structs.iter_mut().map(|s| &mut s.handle));
393+
/// ```
394+
pub fn repair_handles<'a, I>(&self, removed: H, handles: I)
395+
where
396+
I: IntoIterator<Item = &'a mut H>,
397+
H: 'a + PartialOrd,
398+
{
399+
for h in handles {
400+
if *h > removed {
401+
// We rely on the generic H <-> usize conversion to perform the decrement.
402+
// We can safely unwrap here because:
403+
// 1. If h > removed, h must be >= 1.
404+
// 2. h - 1 is guaranteed to be a valid index that previously existed.
405+
if let Ok(idx) = usize::try_from(*h)
406+
&& let Ok(shifted) = H::try_from(idx - 1)
407+
{
408+
*h = shifted;
409+
}
410+
}
411+
}
412+
}
413+
368414
/// Current capacity, in number of items.
369415
#[inline]
370416
pub fn capacity(&self) -> usize {
@@ -1049,4 +1095,81 @@ mod tests {
10491095
assert_eq!(handles[3], 2);
10501096
assert_eq!(interner.resolve(handles[3]), Some(&"D".to_string()));
10511097
}
1098+
1099+
#[test]
1100+
fn test_remove_and_recover_handles_helper() {
1101+
let mut interner = create_string_interner();
1102+
1103+
let mut handles = alloc::vec![
1104+
interner.intern_ref("A").unwrap(), // 0
1105+
interner.intern_ref("B").unwrap(), // 1
1106+
interner.intern_ref("C").unwrap(), // 2
1107+
interner.intern_ref("D").unwrap(), // 3
1108+
];
1109+
1110+
// 1. Remove "B" (index 1).
1111+
let (removed_handle, val) = interner.remove("B").unwrap();
1112+
assert_eq!(val, "B");
1113+
1114+
// 2. REPAIR AUTOMATICALLY
1115+
// We pass a mutable reference to the vector (which is IntoIterator)
1116+
interner.repair_handles(removed_handle, &mut handles);
1117+
1118+
// 3. Verify
1119+
assert_eq!(interner.resolve(handles[0]), Some(&"A".to_string()));
1120+
assert_eq!(interner.resolve(handles[1]), Some(&"C".to_string())); // Was 1, still 1, now points to C
1121+
assert_eq!(interner.resolve(handles[2]), Some(&"C".to_string())); // Was 2, fixed to 1, points to C
1122+
assert_eq!(interner.resolve(handles[3]), Some(&"D".to_string())); // Was 3, fixed to 2, points to D
1123+
}
1124+
1125+
#[test]
1126+
fn test_repair_handles_in_structs() {
1127+
struct User {
1128+
name_handle: u32,
1129+
_score: i32,
1130+
}
1131+
1132+
let mut interner = create_string_interner();
1133+
let h_a = interner.intern_ref("A").unwrap(); // 0
1134+
let h_b = interner.intern_ref("B").unwrap(); // 1
1135+
let h_c = interner.intern_ref("C").unwrap(); // 2
1136+
1137+
let mut users = alloc::vec![
1138+
User {
1139+
name_handle: h_a,
1140+
_score: 10,
1141+
},
1142+
User {
1143+
name_handle: h_b,
1144+
_score: 20,
1145+
},
1146+
User {
1147+
name_handle: h_c,
1148+
_score: 30,
1149+
},
1150+
];
1151+
1152+
// Remove "A" (Handle 0). Everything > 0 should shift down.
1153+
let (removed, _) = interner.remove("A").unwrap();
1154+
1155+
// Complex usage: map to the field
1156+
interner.repair_handles(removed, users.iter_mut().map(|u| &mut u.name_handle));
1157+
1158+
// Validation
1159+
// A was removed.
1160+
// B (was 1) should become 0.
1161+
// C (was 2) should become 1.
1162+
1163+
assert_eq!(users[1].name_handle, 0);
1164+
assert_eq!(
1165+
interner.resolve(users[1].name_handle),
1166+
Some(&"B".to_string())
1167+
);
1168+
1169+
assert_eq!(users[2].name_handle, 1);
1170+
assert_eq!(
1171+
interner.resolve(users[2].name_handle),
1172+
Some(&"C".to_string())
1173+
);
1174+
}
10521175
}

0 commit comments

Comments
 (0)