Push and Pop

Alright. We can initialize. We can allocate. Let’s actually implement somefunctionality! Let’s start with push. All it needs to do is check if we’refull to grow, unconditionally write to the next index, and then increment ourlength.

To do the write we have to be careful not to evaluate the memory we want to writeto. At worst, it’s truly uninitialized memory from the allocator. At best it’s thebits of some old value we popped off. Either way, we can’t just index to the memoryand dereference it, because that will evaluate the memory as a valid instance ofT. Worse, foo[idx] = x will try to call drop on the old value of foo[idx]!

The correct way to do this is with ptr::write, which just blindly overwrites thetarget address with the bits of the value we provide. No evaluation involved.

For push, if the old len (before push was called) is 0, then we want to writeto the 0th index. So we should offset by the old len.

  1. pub fn push(&mut self, elem: T) {
  2. if self.len == self.cap { self.grow(); }
  3. unsafe {
  4. ptr::write(self.ptr.offset(self.len as isize), elem);
  5. }
  6. // Can't fail, we'll OOM first.
  7. self.len += 1;
  8. }

Easy! How about pop? Although this time the index we want to access isinitialized, Rust won’t just let us dereference the location of memory to movethe value out, because that would leave the memory uninitialized! For this weneed ptr::read, which just copies out the bits from the target address andinterprets it as a value of type T. This will leave the memory at this addresslogically uninitialized, even though there is in fact a perfectly good instanceof T there.

For pop, if the old len is 1, we want to read out of the 0th index. So weshould offset by the new len.

  1. pub fn pop(&mut self) -> Option<T> {
  2. if self.len == 0 {
  3. None
  4. } else {
  5. self.len -= 1;
  6. unsafe {
  7. Some(ptr::read(self.ptr.offset(self.len as isize)))
  8. }
  9. }
  10. }