diff --git a/library/alloc/src/collections/linked_list.rs b/library/alloc/src/collections/linked_list.rs index e738c29c237fa..78e615ebb859a 100644 --- a/library/alloc/src/collections/linked_list.rs +++ b/library/alloc/src/collections/linked_list.rs @@ -1855,9 +1855,11 @@ impl<'a, T, A: Allocator> CursorMut<'a, T, A> { // node at index 0, which is expected. if self.list.head == self.current { self.move_next(); - } else { - self.index -= 1; } + // An element was removed before (or at) our current position, so + // the index must be decremented. `saturating_sub` handles the + // ghost node case where index could be 0. + self.index = self.index.saturating_sub(1); self.list.pop_front() } } diff --git a/library/alloc/src/collections/linked_list/tests.rs b/library/alloc/src/collections/linked_list/tests.rs index 3d6c740e80b3b..a51af6345e205 100644 --- a/library/alloc/src/collections/linked_list/tests.rs +++ b/library/alloc/src/collections/linked_list/tests.rs @@ -748,6 +748,60 @@ fn test_cursor_pop_front_back() { assert_eq!(c.index, 2); } +#[test] +fn test_cursor_pop_front_index() { + // Regression test for issue #147616: `pop_front` was not correctly + // updating the cursor index when the cursor was pointing to the front. + + // Test case 1: pop_front when cursor is not at front, then at front + let mut ll: LinkedList = LinkedList::new(); + ll.extend(&[0, 1, 2]); + let mut c = ll.cursor_front_mut(); + + c.move_next(); + assert_eq!(c.index(), Some(1)); + assert_eq!(c.current(), Some(&mut 1)); + + // Pop front when cursor is not at front - index should decrement + c.pop_front(); + assert_eq!(c.index(), Some(0)); + assert_eq!(c.current(), Some(&mut 1)); + + // Now cursor is at front, pop_front again - index should remain 0 + c.pop_front(); + assert_eq!(c.index(), Some(0)); + assert_eq!(c.current(), Some(&mut 2)); + check_links(&ll); + + // Test case 2: minimal reproduction - cursor at front, pop_front + let mut ll: LinkedList = LinkedList::new(); + ll.extend(&[0, 1]); + let mut c = ll.cursor_front_mut(); + + assert_eq!(c.index(), Some(0)); + assert_eq!(c.current(), Some(&mut 0)); + + // Pop front when cursor is at front - should move to next and index stays 0 + c.pop_front(); + assert_eq!(c.index(), Some(0)); + assert_eq!(c.current(), Some(&mut 1)); + check_links(&ll); + + // Test case 3: single element list + let mut ll: LinkedList = LinkedList::new(); + ll.push_back(42); + let mut c = ll.cursor_front_mut(); + + assert_eq!(c.index(), Some(0)); + assert_eq!(c.current(), Some(&mut 42)); + + // Pop the only element - cursor should be at ghost node with index 0 + c.pop_front(); + assert_eq!(c.index(), None); + assert_eq!(c.current(), None); + check_links(&ll); +} + #[test] fn test_extend_ref() { let mut a = LinkedList::new();