Skip to content

Commit df017b9

Browse files
committed
Porting UnsafeSlab to use StableArena by defining a type erased wrapper around NonNull to allow for an easier transition from Vec to StableArena
1 parent 65ac078 commit df017b9

File tree

8 files changed

+393
-398
lines changed

8 files changed

+393
-398
lines changed

simple_bst/src/map/postorder.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ impl<'a, K, V> Iterator for IterPostorder<'a, K, V> {
4242
// Remove right from stack
4343
self.stack.pop();
4444

45-
// Push the current index back onto the stack
45+
// Push the current node back onto the stack
4646
self.stack.push(node);
4747

4848
node = right;

src/arena.rs

+122-30
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::fmt;
2-
use std::ptr;
32
use std::slice;
4-
use std::ptr::NonNull;
3+
use std::ptr::{self, NonNull};
54
use std::mem::{self, MaybeUninit};
65
use std::marker::PhantomData;
76

@@ -81,6 +80,17 @@ unsafe impl AllocStrategy for ExponentialAlloc {
8180
}
8281
}
8382

83+
/// A type-erased pointer to an element in the arena
84+
///
85+
/// Ensures that elements can still be accessed performantly while also assigning the correct
86+
/// lifetime to references created from this pointer.
87+
///
88+
/// Needed because converting an index to a chunk index and item index can be expensive. Uses
89+
/// `NonNull` so that `Option<Ptr>` gets the layout optimizations that `Option<NonNull>` gets.
90+
#[repr(transparent)]
91+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
92+
pub struct Ptr(NonNull<()>);
93+
8494
/// An arena allocator that guarantees that the addresses produced remain usable regardless of how
8595
/// many items are added.
8696
///
@@ -188,11 +198,35 @@ impl<T> StableArena<T> {
188198
self.capacity
189199
}
190200

201+
/// Returns a reference to a value in the arena
202+
///
203+
/// # Safety
204+
///
205+
/// Calling this method with a pointer that is no longer valid is undefined behavior even if the
206+
/// resulting reference is not used.
207+
pub unsafe fn get_unchecked(&self, ptr: Ptr) -> &T {
208+
// Safety: This pointer originated from a `NonNull<T>` so it is safe to cast it back
209+
// (Technically it was a `NonNull<MaybeUninit<T>>` but that doesn't make a difference.)
210+
&*ptr.0.cast().as_ptr()
211+
}
212+
213+
/// Returns a mutable reference to a value in the arena
214+
///
215+
/// # Safety
216+
///
217+
/// Calling this method with a pointer that is no longer valid is undefined behavior even if the
218+
/// resulting reference is not used.
219+
pub unsafe fn get_unchecked_mut(&mut self, ptr: Ptr) -> &mut T {
220+
// Safety: This pointer originated from a `NonNull<T>` so it is safe to cast it back
221+
// (Technically it was a `NonNull<MaybeUninit<T>>` but that doesn't make a difference.)
222+
&mut *ptr.0.cast().as_ptr()
223+
}
224+
191225
/// Allocates the given value in the arena and returns a stable address to the value
192226
///
193227
/// The returned pointer is guaranteed to be valid as long as no method is called that would
194228
/// invalidate the pointer (e.g. the `clear` method).
195-
pub fn alloc(&mut self, value: T) -> NonNull<T> {
229+
pub fn alloc(&mut self, value: T) -> Ptr {
196230
debug_assert!(self.len <= self.capacity);
197231
// The length can never exceed the capacity
198232
if self.len == self.capacity {
@@ -215,7 +249,7 @@ impl<T> StableArena<T> {
215249
self.len += 1;
216250

217251
// Safety: `MaybeUninit<T>` is guaranteed to have the same size, alignment, and ABI as T
218-
unsafe { mem::transmute(item_ptr) }
252+
Ptr(unsafe { mem::transmute(item_ptr) })
219253
}
220254

221255
/// Returns an iterator over the arena
@@ -367,16 +401,6 @@ impl<T> StableArena<T> {
367401
}
368402
}
369403

370-
impl<T> IntoIterator for StableArena<T> {
371-
type Item = T;
372-
373-
type IntoIter = IntoIter<T>;
374-
375-
fn into_iter(self) -> Self::IntoIter {
376-
todo!()
377-
}
378-
}
379-
380404
//TODO: This needs a `#[may_dangle]` attribute on `T`
381405
// See: https://forge.rust-lang.org/libs/maintaining-std.html#is-there-a-manual-drop-implementation
382406
impl<T> Drop for StableArena<T> {
@@ -396,6 +420,54 @@ impl<T> Drop for StableArena<T> {
396420
}
397421
}
398422

423+
impl<T> IntoIterator for StableArena<T> {
424+
type Item = T;
425+
426+
type IntoIter = IntoIter<T>;
427+
428+
fn into_iter(self) -> Self::IntoIter {
429+
todo!()
430+
}
431+
}
432+
433+
impl<'a, T> IntoIterator for &'a StableArena<T> {
434+
type Item = &'a T;
435+
436+
type IntoIter = Iter<'a, T>;
437+
438+
fn into_iter(self) -> Self::IntoIter {
439+
self.iter()
440+
}
441+
}
442+
443+
impl<'a, T> IntoIterator for &'a mut StableArena<T> {
444+
type Item = &'a mut T;
445+
446+
type IntoIter = IterMut<'a, T>;
447+
448+
fn into_iter(self) -> Self::IntoIter {
449+
self.iter_mut()
450+
}
451+
}
452+
453+
pub struct IntoIter<T> {
454+
_marker: PhantomData<T>, //TODO
455+
}
456+
457+
impl<T> Iterator for IntoIter<T> {
458+
type Item = T;
459+
460+
fn next(&mut self) -> Option<Self::Item> {
461+
self.next_ptr().map(|(_, item)| item)
462+
}
463+
}
464+
465+
impl<T> ArenaIterator for IntoIter<T> {
466+
fn next_ptr(&mut self) -> Option<(Ptr, Self::Item)> {
467+
todo!()
468+
}
469+
}
470+
399471
pub struct Iter<'a, T> {
400472
_marker: PhantomData<&'a T>, //TODO
401473
}
@@ -404,6 +476,12 @@ impl<'a, T> Iterator for Iter<'a, T> {
404476
type Item = &'a T;
405477

406478
fn next(&mut self) -> Option<Self::Item> {
479+
self.next_ptr().map(|(_, item)| item)
480+
}
481+
}
482+
483+
impl<'a, T> ArenaIterator for Iter<'a, T> {
484+
fn next_ptr(&mut self) -> Option<(Ptr, Self::Item)> {
407485
todo!()
408486
}
409487
}
@@ -416,19 +494,35 @@ impl<'a, T> Iterator for IterMut<'a, T> {
416494
type Item = &'a mut T;
417495

418496
fn next(&mut self) -> Option<Self::Item> {
497+
self.next_ptr().map(|(_, item)| item)
498+
}
499+
}
500+
501+
impl<'a, T> ArenaIterator for IterMut<'a, T> {
502+
fn next_ptr(&mut self) -> Option<(Ptr, Self::Item)> {
419503
todo!()
420504
}
421505
}
422506

423-
pub struct IntoIter<T> {
424-
_marker: PhantomData<T>, //TODO
507+
pub trait ArenaIterator: Sized + Iterator {
508+
/// Returns the next element of this iterator and its corresponding pointer
509+
fn next_ptr(&mut self) -> Option<(Ptr, Self::Item)>;
510+
511+
/// Returns an iterator that also yields the corresponding pointer for each element
512+
fn pointers(self) -> Pointers<Self> {
513+
Pointers {iter: self}
514+
}
425515
}
426516

427-
impl<T> Iterator for IntoIter<T> {
428-
type Item = T;
517+
pub struct Pointers<I: ArenaIterator> {
518+
iter: I,
519+
}
520+
521+
impl<I: ArenaIterator> Iterator for Pointers<I> {
522+
type Item = (Ptr, I::Item);
429523

430524
fn next(&mut self) -> Option<Self::Item> {
431-
todo!()
525+
self.iter.next_ptr()
432526
}
433527
}
434528

@@ -483,19 +577,17 @@ mod tests {
483577
#[cfg(miri)]
484578
const ALLOCS: usize = 32;
485579

486-
let mut addrs = Vec::with_capacity(ALLOCS);
580+
let mut ptrs = Vec::with_capacity(ALLOCS);
487581

488582
// The arena allocator will resize multiple times during this test
489583
let mut arena = StableArena::new();
490584
for i in 0..ALLOCS {
491585
// Pushing a type that implements drop
492-
addrs.push(arena.alloc(Rc::new(i)));
586+
ptrs.push(arena.alloc(Rc::new(i)));
493587

494588
// Check that all addresses are still valid
495-
for (j, addr) in addrs.iter().enumerate() {
496-
unsafe {
497-
assert_eq!(**addr.as_ref(), j);
498-
}
589+
for (j, ptr) in ptrs.iter().enumerate() {
590+
assert_eq!(unsafe { **arena.get_unchecked(*ptr) }, j);
499591
}
500592
}
501593
}
@@ -523,9 +615,9 @@ mod tests {
523615
// push should not change capacity if capacity is greater than length
524616
assert!(arena.capacity() > arena.len());
525617

526-
let mut addrs = Vec::new();
618+
let mut ptrs = Vec::new();
527619
for i in 0.. {
528-
addrs.push(arena.alloc(i.to_string()));
620+
ptrs.push(arena.alloc(i.to_string()));
529621

530622
if arena.capacity() <= arena.len() {
531623
break;
@@ -538,8 +630,8 @@ mod tests {
538630
// shrink to fit should not affect items
539631
arena.shrink_to_fit();
540632
let capacity = arena.capacity();
541-
for (i, addr) in addrs.iter().copied().enumerate() {
542-
assert_eq!(unsafe { addr.as_ref() }, &i.to_string());
633+
for (i, ptr) in ptrs.iter().copied().enumerate() {
634+
assert_eq!(unsafe { arena.get_unchecked(ptr) }, &i.to_string());
543635
assert_eq!(arena.capacity(), capacity);
544636
}
545637

@@ -551,7 +643,7 @@ mod tests {
551643

552644
//TODO: Add back once we have `pop()`
553645
// pop should not change capacity
554-
// for index in indexes {
646+
// for _ in ptrs {
555647
// arena.pop();
556648
// assert_eq!(slab.capacity(), capacity);
557649
// }

0 commit comments

Comments
 (0)