Iterators and IntoIter

awesome — time to give our vector a soul: borrowing. this is where MyVec<T> starts behaving like a real rust collection with slices and iterators.


🧭 Reinventing from Scratch: Building Vec<T> the Hard Way

Chapter 7 — Iterators & Slices

“Contiguity is power. Borrow it safely and you get the entire iterator ecosystem for free.”


7.1 what we’re building

  • as_slice(&self) -> &[T] and as_mut_slice(&mut self) -> &mut [T]
  • Deref<Target = [T]> & DerefMut so &*myvec behaves like a slice
  • iter(), iter_mut(), and into_iter()
  • iterator types implementing Iterator, DoubleEndedIterator, ExactSizeIterator, FusedIterator
  • IntoIterator for MyVec<T>, &MyVec<T>, and &mut MyVec<T>

⚠️ sidenote — creating slices from raw pointers

Rust’s “door” from raw memory to safe borrowing:

// SAFETY: caller must guarantee the memory is initialized,
// properly aligned, and lives for the returned borrow.
unsafe fn from_raw(len: usize, ptr: *const T) -> &[T] {
    std::slice::from_raw_parts(ptr, len)
}
unsafe fn from_raw_mut(len: usize, ptr: *mut T) -> &mut [T] {
    std::slice::from_raw_parts_mut(ptr, len)
}

For our MyVec<T>, the invariants we already maintain make this safe:

  • elements [0..len) are initialized
  • allocation is aligned for T
  • the borrow does not outlive &self/&mut self

7.2 slices: the universal interface

impl<T> MyVec<T> {
    pub fn as_slice(&self) -> &[T] {
        unsafe { std::slice::from_raw_parts(self.buf.ptr(), self.len) }
    }
    pub fn as_mut_slice(&mut self) -> &mut [T] {
        unsafe { std::slice::from_raw_parts_mut(self.buf.ptr(), self.len) }
    }
}

Deref to slice

This unlocks almost the entire slice API automatically (sort, binary_search, indexing, etc.).

use std::ops::{Deref, DerefMut};

impl<T> Deref for MyVec<T> {
    type Target = [T];
    fn deref(&self) -> &Self::Target { self.as_slice() }
}
impl<T> DerefMut for MyVec<T> {
    fn deref_mut(&mut self) -> &mut Self::Target { self.as_mut_slice() }
}

⚠️ warning: if len is wrong, you’re handing out a lie as a borrow. the compiler trusts you; don’t betray it.


7.3 a simple iter() and iter_mut()

We can delegate to slices:

impl<T> MyVec<T> {
    pub fn iter(&self) -> std::slice::Iter<'_, T> {
        self.as_slice().iter()
    }
    pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, T> {
        self.as_mut_slice().iter_mut()
    }
}

boom — you get next, rev, len, adapters, everything.


7.4 custom iterator types (educational)

Let’s also implement our own minimal iterators to see the machinery.

pub struct Iter<'a, T> {
    start: *const T,
    end: *const T, // one-past-last
    _marker: std::marker::PhantomData<&'a T>,
}
pub struct IterMut<'a, T> {
    start: *mut T,
    end: *mut T,
    _marker: std::marker::PhantomData<&'a mut T>,
}

impl<T> MyVec<T> {
    pub fn raw_iter(&self) -> Iter<'_, T> {
        unsafe {
            let start = self.buf.ptr() as *const T;
            Iter { start, end: start.add(self.len), _marker: Default::default() }
        }
    }
    pub fn raw_iter_mut(&mut self) -> IterMut<'_, T> {
        unsafe {
            let start = self.buf.ptr();
            IterMut { start, end: start.add(self.len), _marker: Default::default() }
        }
    }
}

iterator impls

impl<'a, T> Iterator for Iter<'a, T> {
    type Item = &'a T;
    fn next(&mut self) -> Option<Self::Item> {
        if self.start == self.end { return None; }
        unsafe {
            let cur = &*self.start;
            self.start = self.start.add(1);
            Some(cur)
        }
    }
    fn size_hint(&self) -> (usize, Option<usize>) {
        let n = (self.end as usize - self.start as usize) / std::mem::size_of::<T>();
        (n, Some(n))
    }
}
impl<'a, T> DoubleEndedIterator for Iter<'a, T> {
    fn next_back(&mut self) -> Option<Self::Item> {
        if self.start == self.end { return None; }
        unsafe {
            self.end = self.end.sub(1);
            Some(&*self.end)
        }
    }
}
impl<'a, T> ExactSizeIterator for Iter<'a, T> {}
impl<'a, T> std::iter::FusedIterator for Iter<'a, T> {}

impl<'a, T> Iterator for IterMut<'a, T> {
    type Item = &'a mut T;
    fn next(&mut self) -> Option<Self::Item> {
        if self.start == self.end { return None; }
        unsafe {
            let cur = &mut *self.start;
            self.start = self.start.add(1);
            Some(cur)
        }
    }
    fn size_hint(&self) -> (usize, Option<usize>) {
        let n = (self.end as usize - self.start as usize) / std::mem::size_of::<T>();
        (n, Some(n))
    }
}
impl<'a, T> DoubleEndedIterator for IterMut<'a, T> {
    fn next_back(&mut self) -> Option<Self::Item> {
        if self.start == self.end { return None; }
        unsafe {
            self.end = self.end.sub(1);
            Some(&mut *self.end)
        }
    }
}
impl<'a, T> ExactSizeIterator for IterMut<'a, T> {}
impl<'a, T> std::iter::FusedIterator for IterMut<'a, T> {}

⚠️ aliasing safety: IterMut must yield unique mutable refs. our construction ensures we hand each slot out once, in order, with no overlap.


7.5 moving iteration: into_iter()

There are three flavors of IntoIterator in Rust:

  1. for x in vby value, consumes v, yields T
  2. for x in &v → by shared ref, yields &T
  3. for x in &mut v → by mut ref, yields &mut T

We’ll support all three.

7.5.1 owning iterator (moves out)

pub struct IntoIter<T> {
    buf: *mut T,
    start: *mut T,
    end: *mut T, // one-past-last
    cap: usize,
}

impl<T> IntoIterator for MyVec<T> {
    type Item = T;
    type IntoIter = IntoIter<T>;
    fn into_iter(mut self) -> Self::IntoIter {
        let ptr = self.buf.ptr();
        let len = self.len;
        let cap = self.buf.capacity();

        // prevent MyVec::drop from running element drops;
        // we will consume elements and then deallocate.
        self.len = 0;

        std::mem::forget(self); // we’ll manually dealloc in iterator’s Drop
        IntoIter { buf: ptr, start: ptr, end: unsafe { ptr.add(len) }, cap }
    }
}

impl<T> Iterator for IntoIter<T> {
    type Item = T;
    fn next(&mut self) -> Option<T> {
        if self.start == self.end { return None; }
        unsafe {
            let v = std::ptr::read(self.start);
            self.start = self.start.add(1);
            Some(v)
        }
    }
    fn size_hint(&self) -> (usize, Option<usize>) {
        let n = (self.end as usize - self.start as usize) / std::mem::size_of::<T>();
        (n, Some(n))
    }
}
impl<T> DoubleEndedIterator for IntoIter<T> {
    fn next_back(&mut self) -> Option<T> {
        if self.start == self.end { return None; }
        unsafe {
            self.end = self.end.sub(1);
            Some(std::ptr::read(self.end))
        }
    }
}
impl<T> ExactSizeIterator for IntoIter<T> {}
impl<T> std::iter::FusedIterator for IntoIter<T> {}

impl<T> Drop for IntoIter<T> {
    fn drop(&mut self) {
        // drop any unyielded elements
        while self.start != self.end {
            unsafe {
                std::ptr::drop_in_place(self.start);
                self.start = self.start.add(1);
            }
        }
        // free buffer
        if self.cap != 0 {
            unsafe {
                let layout = std::alloc::Layout::array::<T>(self.cap).unwrap();
                std::alloc::dealloc(self.buf.cast(), layout);
            }
        }
    }
}

⚠️ panic safety: if an adaptor panics mid-iteration, this Drop ensures remaining elements are dropped and memory is freed.

7.5.2 borrowed forms delegate to slices

impl<'a, T> IntoIterator for &'a MyVec<T> {
    type Item = &'a T;
    type IntoIter = std::slice::Iter<'a, T>;
    fn into_iter(self) -> Self::IntoIter { self.iter() }
}

impl<'a, T> IntoIterator for &'a mut MyVec<T> {
    type Item = &'a mut T;
    type IntoIter = std::slice::IterMut<'a, T>;
    fn into_iter(self) -> Self::IntoIter { self.iter_mut() }
}

7.6 ergonomic extras

indexing (via slice)

use std::ops::{Index, IndexMut};

impl<T> Index<usize> for MyVec<T> {
    type Output = T;
    fn index(&self, i: usize) -> &Self::Output { &self.as_slice()[i] }
}
impl<T> IndexMut<usize> for MyVec<T> {
    fn index_mut(&mut self, i: usize) -> &mut Self::Output { &mut self.as_mut_slice()[i] }
}

as_ptr / as_mut_ptr pass-throughs (useful for FFI)

impl<T> MyVec<T> {
    pub fn as_ptr(&self) -> *const T { self.buf.ptr() }
    pub fn as_mut_ptr(&mut self) -> *mut T { self.buf.ptr() }
}

🔍 visualization — lifetimes and borrows

&MyVec<T> ──deref──▶ &[T] ──iter()──▶ Iter<'_, T> yields &T
&mut MyVec<T> ─────▶ &mut [T] ──────▶ IterMut<'_, T> yields &mut T
MyVec<T> (owned) ───▶ IntoIter<T> moves T out
  • borrowed iterators do not take ownership; they just walk references guaranteed by the slice’s lifetime
  • owning iterator does take ownership; it must handle element drops and memory dealloc on Drop

🧠 chapter takeaways

feature why it matters
from_raw_parts{,_mut} the safe bridge from raw to borrowed
Deref<Target=[T]> unlocks slice API for free
Iter, IterMut, IntoIter three iteration modes: shared, unique, owning
ExactSize, Fused, DoubleEnded enables rich adaptor semantics (rev, collect, size hints)
panic-safe iterator Drop no leaks even on early abort

golden invariant still rules: slices and iterators must only walk [0..len) — the initialized region.


next → Chapter 8: Panic Safety & Invariants

we’ll add reserve, reserve_exact, talk about try_reserve, capacity growth strategies, and show panic guards that keep your vector valid even when constructors or moves explode mid-operation.