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]andas_mut_slice(&mut self) -> &mut [T]Deref<Target = [T]>&DerefMutso&*myvecbehaves like a sliceiter(),iter_mut(), andinto_iter()- iterator types implementing
Iterator,DoubleEndedIterator,ExactSizeIterator,FusedIterator IntoIteratorforMyVec<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
lenis 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:
IterMutmust 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:
for x in v→ by value, consumesv, yieldsTfor x in &v→ by shared ref, yields&Tfor 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
Dropensures 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.