% Example: Implementing Vec To bring everything together, we're going to write `std::Vec` from scratch. Because the all the best tools for writing unsafe code are unstable, this project will only work on nightly (as of Rust 1.2.0). First off, we need to come up with the struct layout. Naively we want this design: ``` struct Vec { ptr: *mut T, cap: usize, len: usize, } ``` And indeed this would compile. Unfortunately, it would be incorrect. The compiler will give us too strict variance, so e.g. an `&Vec<&'static str>` couldn't be used where an `&Vec<&'a str>` was expected. More importantly, it will give incorrect ownership information to dropck, as it will conservatively assume we don't own any values of type `T`. See [the chapter on ownership and lifetimes] (lifetimes.html) for details. As we saw in the lifetimes chapter, we should use `Unique` in place of `*mut T` when we have a raw pointer to an allocation we own: ``` #![feature(unique)] use std::ptr::Unique; pub struct Vec { ptr: Unique, cap: usize, len: usize, } ``` As a recap, Unique is a wrapper around a raw pointer that declares that: * We own at least one value of type `T` * We are Send/Sync iff `T` is Send/Sync * Our pointer is never null (and therefore `Option` is null-pointer-optimized) That last point is subtle. First, it makes `Unique::new` unsafe to call, because putting `null` inside of it is Undefined Behaviour. It also throws a wrench in an important feature of Vec (and indeed all of the std collections): an empty Vec doesn't actually allocate at all. So if we can't allocate, but also can't put a null pointer in `ptr`, what do we do in `Vec::new`? Well, we just put some other garbage in there! This is perfectly fine because we already have `cap == 0` as our sentinel for no allocation. We don't even need to handle it specially in almost any code because we usually need to check if `cap > len` or `len > 0` anyway. The traditional Rust value to put here is `0x01`. The standard library actually exposes this as `std::rt::heap::EMPTY`. There are quite a few places where we'll want to use `heap::EMPTY` because there's no real allocation to talk about but `null` would make the compiler angry. All of the `heap` API is totally unstable under the `alloc` feature, though. We could trivially define `heap::EMPTY` ourselves, but we'll want the rest of the `heap` API anyway, so let's just get that dependency over with. So: ```rust #![feature(alloc)] use std::rt::heap::EMPTY; use std::mem; impl Vec { fn new() -> Self { assert!(mem::size_of::() != 0, "We're not ready to handle ZSTs"); unsafe { // need to cast EMPTY to the actual ptr type we want, let // inference handle it. Vec { ptr: Unique::new(heap::EMPTY as *mut _), len: 0, cap: 0 } } } } ``` I slipped in that assert there because zero-sized types will require some special handling throughout our code, and I want to defer the issue for now. Without this assert, some of our early drafts will do some Very Bad Things. Next we need to figure out what to actually do when we *do* want space. For that, we'll need to use the rest of the heap APIs. These basically allow us to talk directly to Rust's instance of jemalloc. We'll also need a way to handle out-of-memory conditions. The standard library calls the `abort` intrinsic, but calling intrinsics from normal Rust code is a pretty bad idea. Unfortunately, the `abort` exposed by the standard library allocates. Not something we want to do during `oom`! Instead, we'll call `std::process::exit`. ```rust fn oom() { ::std::process::exit(-9999); } ``` Okay, now we can write growing: ```rust fn grow(&mut self) { unsafe { let align = mem::min_align_of::(); let elem_size = mem::size_of::(); let (new_cap, ptr) = if self.cap == 0 { let ptr = heap::allocate(elem_size, align); (1, ptr) } else { let new_cap = 2 * self.cap; let ptr = heap::reallocate(*self.ptr as *mut _, self.cap * elem_size, new_cap * elem_size, align); (new_cap, ptr) }; // If allocate or reallocate fail, we'll get `null` back if ptr.is_null() { oom() } self.ptr = Unique::new(ptr as *mut _); self.cap = new_cap; } } ``` There's nothing particularly tricky in here: if we're totally empty, we need to do a fresh allocation. Otherwise, we need to reallocate the current pointer. Although we have a subtle bug here with the multiply overflow. TODO: rest of this