Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 121 additions & 20 deletions src/arena.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use core::convert::TryInto;
use core::mem::replace;
use core::ops;
use core::{cmp, ops};

// Vec is part of the prelude when std is enabled.
#[cfg(not(feature = "std"))]
Expand Down Expand Up @@ -442,26 +442,29 @@ impl<T> Arena<T> {
panic!("Arena::get2_mut is called with two identical indices");
}

// Same entry with a different generation. We'll prefer the first value
// that matches.
if index1.slot == index2.slot {
// The borrow checker forces us to index into our storage twice here
// due to `return` extending borrows.
if self.get(index1).is_some() {
return (self.get_mut(index1), None);
} else {
return (None, self.get_mut(index2));
let (entry1, entry2) = match index1.slot.cmp(&index2.slot) {
// Same entry with a different generation. We'll prefer the first value
// that matches.
cmp::Ordering::Equal => {
if self.get(index1).is_some() {
return (self.get_mut(index1), None);
} else {
return (None, self.get_mut(index2));
}
}
// If the indices point to different slots, we can mutably split the
// underlying storage to get the desired entry in each slice.
cmp::Ordering::Less => {
let (slice1, slice2) = self.storage.split_at_mut(index2.slot as usize);
(slice1.get_mut(index1.slot as usize), slice2.get_mut(0))
}
}

// If the indices point to different slots, we can mutably split the
// underlying storage to get the desired entry in each slice.
let (entry1, entry2) = if index1.slot > index2.slot {
let (slice1, slice2) = self.storage.split_at_mut(index1.slot as usize);
(slice2.get_mut(0), slice1.get_mut(index2.slot as usize))
} else {
let (slice1, slice2) = self.storage.split_at_mut(index2.slot as usize);
(slice1.get_mut(index1.slot as usize), slice2.get_mut(0))
// If the indices point to different slots, we can mutably split the
// underlying storage to get the desired entry in each slice.
cmp::Ordering::Greater => {
let (slice1, slice2) = self.storage.split_at_mut(index1.slot as usize);
(slice2.get_mut(0), slice1.get_mut(index2.slot as usize))
}
};

(
Expand Down Expand Up @@ -588,7 +591,7 @@ impl<T> Arena<T> {

Some((index, value))
}
_ => None,
Slot::Empty(_) => None,
}
}

Expand Down Expand Up @@ -770,20 +773,60 @@ mod test {
assert_eq!(arena.capacity(), 8);
}

#[test]
fn reservation() {
let mut arena: Arena<u32> = Arena::new();
assert!(arena.is_empty());
assert_eq!(arena.capacity(), 0);

arena.reserve(128);
// we can reserve more than asked, so it's fine to check too much
assert!(arena.capacity() >= 128);
}

#[test]
fn remove() {
let mut arena = Arena::new();
let handle_a = arena.insert(1);

assert_eq!(arena.remove(handle_a), Some(1));
// you can't do it twice...
assert_eq!(arena.remove(handle_a), None);

let handle_b = arena.insert(1);

// we survive the a-b-a problem:
assert!(arena.remove(handle_a).is_none());
assert!(arena.remove(handle_b).is_some());
}

#[test]
fn insert_and_get() {
let mut arena = Arena::new();

let one = arena.insert(1);
assert_eq!(arena.len(), 1);
assert_eq!(arena.get(one), Some(&1));
assert_eq!(arena.contains_slot(one.slot()).unwrap(), one);

let two = arena.insert(2);
assert_eq!(arena.len(), 2);
assert_eq!(arena.get(one), Some(&1));
assert_eq!(arena.get(two), Some(&2));
}

#[test]
fn insert_clear() {
let mut arena = Arena::new();
assert!(arena.is_empty());

arena.insert(1);
assert!(!arena.is_empty());

arena.clear();
assert!(arena.is_empty());
}

#[test]
fn insert_remove_get() {
let mut arena = Arena::new();
Expand Down Expand Up @@ -897,6 +940,15 @@ mod test {

assert_eq!(arena.get(foo), Some(&105));
assert_eq!(arena.get(bar), Some(&505));

let (bar_handle, foo_handle) = arena.get2_mut(bar, foo);
let bar_handle = bar_handle.unwrap();
let foo_handle = foo_handle.unwrap();
*bar_handle = 100;
*foo_handle = 500;

assert_eq!(arena.get(foo), Some(&500));
assert_eq!(arena.get(bar), Some(&100));
}

#[test]
Expand Down Expand Up @@ -982,6 +1034,11 @@ mod test {
let new_a = arena.invalidate(a).unwrap();
assert_eq!(arena.get(a), None);
assert_eq!(arena.get(new_a), Some(&"a"));

// you also can't invalidate with the old index:
assert_eq!(arena.invalidate(a), None);
// and that didn't actually invalidate:
assert_eq!(arena.get(new_a), Some(&"a"));
}

#[test]
Expand All @@ -1007,6 +1064,16 @@ mod test {
assert_eq!(index.to_bits(), 0x1BAD_CAFE_DEAD_BEEF);
}

#[test]
fn index_properties() {
let index = Index {
slot: 123,
generation: Generation::from_u32(456).unwrap(),
};
assert_eq!(index.slot(), 123);
assert_eq!(index.generation(), 456);
}

#[test]
fn index_bits_none_on_zero_generation() {
let index = Index::from_bits(0x0000_0000_DEAD_BEEF);
Expand Down Expand Up @@ -1055,4 +1122,38 @@ mod test {
// we moved the goalpost, so the next_index won't be accurate anymore
assert_ne!(next, next_next_insert);
}

#[test]
fn aba_test() {

@LPGhatguy LPGhatguy Sep 15, 2026 •

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I legitimately internalized this as the ABBA problem and so every time you said "ABA" IRL I was like "isn't it ABBA?"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the ABBA problem is when you have too many men after midnight

let mut arena = Arena::new();
let index_a = arena.insert('a');
assert!(arena.get_mut(index_a).is_some());

arena.remove(index_a);
assert!(arena.get_mut(index_a).is_none());

let index_b = arena.insert('a');
assert!(arena.get_mut(index_b).is_some());

// we solve the a-b-a problem
assert!(arena.get_mut(index_a).is_none());
assert_eq!(arena.contains_slot(index_a.slot()).unwrap(), index_b);

assert_ne!(index_a, index_b);
}

#[test]
fn get_by_slot() {
let mut arena = Arena::new();
let handle_a = arena.insert("a");
let handle_b = arena.insert("b");
arena.remove(handle_b).unwrap();

assert_eq!(
arena.get_by_slot_mut(handle_a.slot()),
Some((handle_a, &mut "a"))
);
assert_eq!(arena.get_by_slot_mut(handle_b.slot()), None);
assert_eq!(arena.get_by_slot_mut(u32::MAX), None);
}
}
3 changes: 3 additions & 0 deletions src/free_pointer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ mod test {
fn from_slot() {
let ptr = FreePointer::from_slot(0);
assert_eq!(ptr.slot(), 0);

let ptr = FreePointer::from_slot(123456);
assert_eq!(ptr.slot(), 123456);
}

#[test]
Expand Down
14 changes: 14 additions & 0 deletions src/iter/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,18 @@ mod test {
assert!(pairs.contains(&(three, &3)));
assert!(pairs.contains(&(four, &4)));
}

#[test]
fn iter_into_iter() {
let mut arena = Arena::with_capacity(2);
arena.insert(1);
arena.insert(2);

let arena = &arena;
for (idx, value) in arena.into_iter() {
assert_eq!(idx.slot() + 1, *value);
}

assert_eq!(arena.into_iter().count(), 2);
}
}
14 changes: 14 additions & 0 deletions src/iter/iter_mut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,18 @@ mod test {
assert!(pairs.contains(&(three, &mut 3)));
assert!(pairs.contains(&(four, &mut 4)));
}

#[test]
fn iter_mut_into_iter() {
let mut arena = Arena::with_capacity(2);
arena.insert(1);
arena.insert(2);

let arena = &mut arena;
for (idx, value) in arena.into_iter() {
assert_eq!(idx.slot() + 1, *value);
}

assert_eq!(arena.into_iter().count(), 2);
}
}
Loading