Layout

First off, we need to come up with the struct layout. A Vec has three parts: a pointer to the allocation, the size of the allocation, and the number of elements that have been initialized.

Naively, this means we just want this design:

pub struct Vec<T> {
    ptr: *mut T,
    cap: usize,
    len: usize,
}

And indeed this would compile. Unfortunately, it would be incorrect. First, the compiler will give us too strict variance. So a &Vec<&'static str> couldn’t be used where an &Vec<&'a str> was expected. More importantly, it will give incorrect ownership information to the drop checker, as it will conservatively assume we don’t own any values of type T. See the chapter on ownership and lifetimes for all the details on variance and drop check.

As we saw in the ownership chapter, the standard library uses Unique<T> in place of *mut T when it has a raw pointer to an allocation that it owns. Unique is unstable, so we’d like to not use it if possible, though.

As a recap, Unique is a wrapper around a raw pointer that declares that:

  • We are covariant over T
  • We may own a value of type T (for drop check)
  • We are Send/Sync if T is Send/Sync
  • Our pointer is never null (so Option<Vec<T>> is null-pointer-optimized)

We can implement all of the above requirements in stable Rust. To do this, instead of using Unique<T> we will use NonNull<T>, another wrapper around a raw pointer, which gives us two of the above properties, namely it is covariant over T and is declared to never be null. By adding a PhantomData<T> (for drop check) and implementing Send/Sync if T is, we get the same results as using Unique<T>:

use std::ptr::NonNull;
use std::marker::PhantomData;

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

unsafe impl<T: Send> Send for Vec<T> {}
unsafe impl<T: Sync> Sync for Vec<T> {}
fn main() {}