Advanced Projects

Custom Vector-Like Collection

A custom dynamic array (RawVec<T> / MiniVec<T>) is a heap-allocated, contiguous, growable buffer implemented directly on top of std::alloc::Layout, std::ptr::NonNull<T>, and pointer arithmetic. Implementing a vector from first principles requires managing raw memory allocation, geometric capacity doubling, uninitialized memory safety, ownership transfer via Deref<Target = [T]>, and deterministic destruction of initialized elements alongside buffer deallocation in Drop.

Memory Layout and Invariants

MiniVec<T> on stack (24 bytes on 64-bit)
+------------------+------------------+------------------+
| ptr: NonNull<T>  | len: usize (3)   | cap: usize (4)   |
+--------+---------+------------------+------------------+
         |
         v  Heap allocation: Layout::array::<T>(cap)
         +---------+---------+---------+-----------------+
         | Elem 0  | Elem 1  | Elem 2  | Uninitialized   |
         +---------+---------+---------+-----------------+
         |<------- initialized ------->|<-- spare cap -->|
Invariant Requirement Enforced By
Non-null dangling pointer when cap == 0 NonNull::dangling() avoids heap allocation for empty vectors MiniVec::new
Initialized range 0..len Only indices < len may be read or dropped push, pop, Drop
Allocation alignment and size Layout::array::<T>(cap) checked for isize::MAX overflow grow, Drop
Zero-Sized Types (size_of::<T>() == 0) Never allocate on heap; set cap = usize::MAX Compile-time assert! or ZST branch
Variance and drop check PhantomData<T> communicates ownership of T to the borrow checker Struct field

Complete Implementation

use std::alloc::{self, Layout};
use std::fmt;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut, Index, IndexMut};
use std::ptr::{self, NonNull};

pub struct MiniVec<T> {
    ptr: NonNull<T>,
    len: usize,
    cap: usize,
    _marker: PhantomData<T>,
}

unsafe impl<T: Send> Send for MiniVec<T> {}
unsafe impl<T: Sync> Sync for MiniVec<T> {}

impl<T> MiniVec<T> {
    pub const fn new() -> Self {
        assert!(
            std::mem::size_of::<T>() != 0,
            "ZSTs are not supported in MiniVec"
        );
        Self {
            ptr: NonNull::dangling(),
            len: 0,
            cap: 0,
            _marker: PhantomData,
        }
    }

    pub fn with_capacity(capacity: usize) -> Self {
        let mut vec = Self::new();
        if capacity > 0 {
            vec.reserve_exact(capacity);
        }
        vec
    }

    pub const fn len(&self) -> usize {
        self.len
    }

    pub const fn is_empty(&self) -> bool {
        self.len == 0
    }

    pub const fn capacity(&self) -> usize {
        self.cap
    }

    pub fn push(&mut self, value: T) {
        if self.len == self.cap {
            self.grow();
        }
        // SAFETY: `self.len < self.cap` is guaranteed after `grow()`,
        // and `self.ptr.as_ptr().add(self.len)` points to valid uninitialized memory.
        unsafe {
            ptr::write(self.ptr.as_ptr().add(self.len), value);
        }
        self.len += 1;
    }

    pub fn pop(&mut self) -> Option<T> {
        if self.len == 0 {
            None
        } else {
            self.len -= 1;
            // SAFETY: The element at `self.len` was initialized and is now logically removed.
            unsafe { Some(ptr::read(self.ptr.as_ptr().add(self.len))) }
        }
    }

    pub fn insert(&mut self, index: usize, element: T) {
        assert!(index <= self.len, "insert index out of bounds");
        if self.len == self.cap {
            self.grow();
        }
        // SAFETY: Shifting `self.len - index` elements right by 1 stays within `self.cap`.
        unsafe {
            let p = self.ptr.as_ptr().add(index);
            if index < self.len {
                ptr::copy(p, p.add(1), self.len - index);
            }
            ptr::write(p, element);
        }
        self.len += 1;
    }

    pub fn remove(&mut self, index: usize) -> T {
        assert!(index < self.len, "remove index out of bounds");
        self.len -= 1;
        // SAFETY: `index` is within initialized bounds; trailing elements shift left by 1.
        unsafe {
            let p = self.ptr.as_ptr().add(index);
            let result = ptr::read(p);
            ptr::copy(p.add(1), p, self.len - index);
            result
        }
    }

    fn reserve_exact(&mut self, new_cap: usize) {
        assert!(new_cap > self.cap);
        let new_layout = Layout::array::<T>(new_cap).expect("allocation capacity overflow");
        assert!(
            new_layout.size() <= isize::MAX as usize,
            "allocation too large"
        );

        let new_ptr = if self.cap == 0 {
            // SAFETY: `new_layout.size() > 0` because `size_of::<T>() > 0` and `new_cap > 0`.
            unsafe { alloc::alloc(new_layout) }
        } else {
            let old_layout = Layout::array::<T>(self.cap).unwrap();
            // SAFETY: `self.ptr` was allocated with `old_layout`, and `new_layout.size() > 0`.
            unsafe { alloc::realloc(self.ptr.as_ptr() as *mut u8, old_layout, new_layout.size()) }
        };

        self.ptr = match NonNull::new(new_ptr as *mut T) {
            Some(p) => p,
            None => alloc::handle_alloc_error(new_layout),
        };
        self.cap = new_cap;
    }

    fn grow(&mut self) {
        let new_cap = if self.cap == 0 { 4 } else { self.cap * 2 };
        self.reserve_exact(new_cap);
    }
}

impl<T> Deref for MiniVec<T> {
    type Target = [T];

    fn deref(&self) -> &[T] {
        // SAFETY: `0..self.len` is initialized and properly aligned.
        unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
    }
}

impl<T> DerefMut for MiniVec<T> {
    fn deref_mut(&mut self) -> &mut [T] {
        // SAFETY: `0..self.len` is initialized and uniquely borrowed via `&mut self`.
        unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
    }
}

impl<T> Index<usize> for MiniVec<T> {
    type Output = T;
    fn index(&self, index: usize) -> &Self::Output {
        &(**self)[index]
    }
}

impl<T> IndexMut<usize> for MiniVec<T> {
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
        &mut (**self)[index]
    }
}

impl<T: fmt::Debug> fmt::Debug for MiniVec<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&**self, f)
    }
}

impl<T> Drop for MiniVec<T> {
    fn drop(&mut self) {
        if self.cap != 0 {
            // Drop all remaining initialized elements in place.
            unsafe {
                ptr::drop_in_place(ptr::slice_from_raw_parts_mut(self.ptr.as_ptr(), self.len));
                let layout = Layout::array::<T>(self.cap).unwrap();
                alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
            }
        }
    }
}

pub struct IntoIter<T> {
    buf: NonNull<T>,
    cap: usize,
    start: *const T,
    end: *const T,
}

impl<T> IntoIterator for MiniVec<T> {
    type Item = T;
    type IntoIter = IntoIter<T>;

    fn into_iter(self) -> Self::IntoIter {
        let ptr = self.ptr;
        let cap = self.cap;
        let len = self.len;
        std::mem::forget(self);

        // SAFETY: `ptr.as_ptr().add(len)` stays within or one-past the allocation.
        let end = unsafe { ptr.as_ptr().add(len) };
        IntoIter {
            buf: ptr,
            cap,
            start: ptr.as_ptr(),
            end,
        }
    }
}

impl<T> Iterator for IntoIter<T> {
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.start == self.end {
            None
        } else {
            // SAFETY: `self.start < self.end` points to an initialized element not yet yielded.
            unsafe {
                let item = ptr::read(self.start);
                self.start = self.start.add(1);
                Some(item)
            }
        }
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let remaining = (self.end as usize - self.start as usize) / std::mem::size_of::<T>();
        (remaining, Some(remaining))
    }
}

impl<T> DoubleEndedIterator for IntoIter<T> {
    fn next_back(&mut self) -> Option<Self::Item> {
        if self.start == self.end {
            None
        } else {
            // SAFETY: Decrementing `self.end` steps onto the last initialized element.
            unsafe {
                self.end = self.end.sub(1);
                Some(ptr::read(self.end))
            }
        }
    }
}

impl<T> Drop for IntoIter<T> {
    fn drop(&mut self) {
        if self.cap != 0 {
            let remaining = (self.end as usize - self.start as usize) / std::mem::size_of::<T>();
            unsafe {
                ptr::drop_in_place(ptr::slice_from_raw_parts_mut(
                    self.start as *mut T,
                    remaining,
                ));
                let layout = Layout::array::<T>(self.cap).unwrap();
                alloc::dealloc(self.buf.as_ptr() as *mut u8, layout);
            }
        }
    }
}

fn main() {
    let mut v = MiniVec::new();
    println!("Initial: len={}, cap={}", v.len(), v.capacity());

    for word in ["alpha", "beta", "gamma", "delta", "epsilon"] {
        v.push(String::from(word));
    }
    println!("After 5 pushes: {:?} (len={}, cap={})", v, v.len(), v.capacity());

    v.insert(2, String::from("inserted"));
    println!("After insert(2): {:?}", v);

    let removed = v.remove(1);
    println!("Removed index 1 ('{}'): {:?}", removed, v);

    // Slice methods work directly through Deref<Target = [T]>
    v.sort();
    println!("Sorted via slice DerefMut: {:?}", v);

    let mut iter = v.into_iter();
    println!("Front: {:?}, Back: {:?}", iter.next(), iter.next_back());
    let rest: Vec<String> = iter.collect();
    println!("Remaining collected: {:?}", rest);
}
Initial: len=0, cap=0
After 5 pushes: ["alpha", "beta", "gamma", "delta", "epsilon"] (len=5, cap=8)
After insert(2): ["alpha", "beta", "inserted", "gamma", "delta", "epsilon"]
Removed index 1 ('beta'): ["alpha", "inserted", "gamma", "delta", "epsilon"]
Sorted via slice DerefMut: ["alpha", "delta", "epsilon", "gamma", "inserted"]
Front: Some("alpha"), Back: Some("inserted")
Remaining collected: ["delta", "epsilon", "gamma"]