@@ -288,6 +288,83 @@ where
288288 self . items . contains ( item)
289289 }
290290
291+ /// Removes a value from the interner and returns the Handle and the Value.
292+ ///
293+ /// # ⚠️ Performance Warning: O(n)
294+ ///
295+ /// Unlike standard `HashMap` removal which is O(1), this operation is **O(n)**
296+ /// (linear time) because the interner is backed by a contiguous vector to
297+ /// preserve ordering.
298+ ///
299+ /// When an item is removed, all subsequent items must be **shifted to the left**
300+ /// to fill the gap.
301+ ///
302+ /// # ⚠️ Handle Invalidation
303+ ///
304+ /// Because indices shift, **handles for items inserted after this one will change**.
305+ ///
306+ /// ## Example Scenario
307+ ///
308+ /// Imagine an interner with items `[A, B, C]` corresponding to handles `0, 1, 2`.
309+ ///
310+ /// 1. You remove `B` (handle `1`).
311+ /// 2. `C` shifts left to fill the gap.
312+ /// 3. The storage is now `[A, C]`.
313+ ///
314+ /// **The Consequence:**
315+ /// * Handle `0` (`A`) remains valid.
316+ /// * Handle `2` (which used to be `C`) is now out of bounds!
317+ /// * Handle `1` (which used to be `B`) now resolves to `C`.
318+ ///
319+ /// # Handle Recovery
320+ ///
321+ /// Since the shift is deterministic, you can "repair" your existing handles
322+ /// if you are tracking them.
323+ ///
324+ /// * **Handles < removed:** Unaffected.
325+ /// * **Handles > removed:** Must be decremented by 1.
326+ ///
327+ /// ```text
328+ /// if my_handle > removed_handle {
329+ /// my_handle -= 1;
330+ /// }
331+ /// ```
332+ pub fn remove < Q > ( & mut self , item : & Q ) -> Option < ( H , T ) >
333+ where
334+ T : Borrow < Q > ,
335+ Q : Hash + Eq + ?Sized ,
336+ {
337+ // shift_remove_full returns (index, value)
338+ // We use shift_remove to preserve the relative order of remaining items.
339+ let ( idx, val) = self . items . shift_remove_full ( item) ?;
340+
341+ // The index returned by IndexSet is guaranteed to fit in usize.
342+ // We convert it back to H to return to the user.
343+ // We suppress the error here because if it was in the map, it had a valid handle.
344+ let handle = H :: try_from ( idx) . ok ( ) ?;
345+
346+ Some ( ( handle, val) )
347+ }
348+
349+ /// Removes the item associated with the given `handle`.
350+ ///
351+ /// # Returns
352+ ///
353+ /// - `Some(T)`: The value that was removed, if the handle was valid.
354+ /// - `None`: If the handle was invalid (e.g. out of bounds).
355+ ///
356+ /// # ⚠️ Performance & Invalidation
357+ ///
358+ /// Like [`remove`](Self::remove), this operation is **O(n)** and will shift
359+ /// the indices of all subsequent items.
360+ ///
361+ /// Any existing handle `h` where `h > handle` must be decremented by 1 to
362+ /// remain valid.
363+ pub fn remove_handle ( & mut self , handle : H ) -> Option < T > {
364+ let idx = usize:: try_from ( handle) . ok ( ) ?;
365+ self . items . shift_remove_index ( idx)
366+ }
367+
291368 /// Current capacity, in number of items.
292369 #[ inline]
293370 pub fn capacity ( & self ) -> usize {
@@ -891,4 +968,85 @@ mod tests {
891968 let found = interner. lookup_handle ( "A" ) . unwrap ( ) ;
892969 assert_eq ! ( found, Some ( h) ) ;
893970 }
971+
972+ #[ test]
973+ fn test_remove_handle_shifts_indices ( ) {
974+ let mut interner = create_string_interner ( ) ;
975+
976+ // 1. Insert [A, B, C]
977+ let h_a = interner. intern_ref ( "A" ) . unwrap ( ) ; // 0
978+ let h_b = interner. intern_ref ( "B" ) . unwrap ( ) ; // 1
979+ let h_c = interner. intern_ref ( "C" ) . unwrap ( ) ; // 2
980+
981+ assert_eq ! ( interner. len( ) , 3 ) ;
982+
983+ // 2. Remove "B" (index 1) using its handle
984+ let removed = interner. remove_handle ( h_b) ;
985+
986+ assert_eq ! ( removed, Some ( "B" . to_string( ) ) ) ;
987+ assert_eq ! ( interner. len( ) , 2 ) ;
988+
989+ // 3. Verify the state of the remaining handles
990+
991+ // Handle 0 ("A") is unaffected because it was *before* the removal.
992+ assert_eq ! ( interner. resolve( h_a) , Some ( & "A" . to_string( ) ) ) ;
993+
994+ // Handle 2 ("C") is now BROKEN. It points to index 2, but the vector
995+ // is only length 2 (indices 0 and 1).
996+ assert_eq ! ( interner. resolve( h_c) , None ) ;
997+
998+ // "C" has actually shifted down to Handle 1.
999+ // (This simulates what happens if we reused the old 'B' handle)
1000+ assert_eq ! ( interner. resolve( h_b) , Some ( & "C" . to_string( ) ) ) ;
1001+ }
1002+
1003+ #[ test]
1004+ fn test_remove_and_recover_handles ( ) {
1005+ let mut interner = create_string_interner ( ) ;
1006+
1007+ // 1. Setup handles: [0, 1, 2, 3]
1008+ // Items: ["A", "B", "C", "D"]
1009+ let mut handles = alloc:: vec![
1010+ interner. intern_ref( "A" ) . unwrap( ) , // 0
1011+ interner. intern_ref( "B" ) . unwrap( ) , // 1
1012+ interner. intern_ref( "C" ) . unwrap( ) , // 2
1013+ interner. intern_ref( "D" ) . unwrap( ) , // 3
1014+ ] ;
1015+
1016+ // 2. Remove "B" (index 1).
1017+ // usage: remove returns the handle of the item that was removed.
1018+ let ( removed_handle, val) = interner. remove ( "B" ) . unwrap ( ) ;
1019+
1020+ assert_eq ! ( val, "B" ) ;
1021+ assert_eq ! ( removed_handle, 1 ) ;
1022+
1023+ // 3. The Recovery Loop
1024+ // We iterate over our local handles and patch them.
1025+ for h in & mut handles {
1026+ // Use strict greater-than (>).
1027+ // Handles < 1 stay the same.
1028+ // Handle == 1 is the one we just removed.
1029+ if * h > removed_handle {
1030+ * h -= 1 ;
1031+ }
1032+ }
1033+
1034+ // 4. Verification
1035+
1036+ // "A" (was 0) should still be 0
1037+ assert_eq ! ( interner. resolve( handles[ 0 ] ) , Some ( & "A" . to_string( ) ) ) ;
1038+
1039+ // "B" (was 1) was removed. In our vector, `handles[1]` is still `1`.
1040+ // However, in the interner, index 1 has been filled by "C".
1041+ // This is expected behavior for the "removed" handle.
1042+ assert_eq ! ( interner. resolve( handles[ 1 ] ) , Some ( & "C" . to_string( ) ) ) ;
1043+
1044+ // "C" (was 2) should have been patched to 1.
1045+ assert_eq ! ( handles[ 2 ] , 1 ) ;
1046+ assert_eq ! ( interner. resolve( handles[ 2 ] ) , Some ( & "C" . to_string( ) ) ) ;
1047+
1048+ // "D" (was 3) should have been patched to 2.
1049+ assert_eq ! ( handles[ 3 ] , 2 ) ;
1050+ assert_eq ! ( interner. resolve( handles[ 3 ] ) , Some ( & "D" . to_string( ) ) ) ;
1051+ }
8941052}
0 commit comments