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 too strict. The compiler will give us too strict variance. So a &Vec<&'static str> couldn't be used where a &Vec<&'a str> was expected. See the chapter on ownership and lifetimes for all the details on variance.

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 (this is not relevant for our example here, but see the chapter on PhantomData on why the real std::vec::Vec<T> needs this)
  • 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 implementing Send/Sync if T is, we get the same results as using Unique<T>:

use std::ptr::NonNull;

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

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