39
Basics
Five operations: new, push_front, pop_front, push_back, pop_back. Each one is a sequence of pointer writes wrapped in unsafe. We allocate with Box::new and convert into raw with Box::into_raw; we free by going back through Box::from_raw. Symmetry between front and back lets us write half the code.
-
push_front: allocate a node, leak the Box into a raw pointer, splice it ahead of the currentfront. Theunsafeblock wraps the dereferences. Three cases would be tempting (empty / single / general) but the symmetry ofOptioncollapses them: iffrontwasNonethen the back end is also empty and we set both.pub fn push_front(&mut self, elem: T) { unsafe { let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node { front: None, back: self.front, elem, }))); match self.front { Some(old) => (*old.as_ptr()).front = Some(new), None => self.back = Some(new), } self.front = Some(new); self.len += 1; } } -
pop_front: take the front pointer, reconstruct theBox<Node<T>>so its destructor will run, splice the new front in.Box::from_rawtakes ownership of the heap allocation back from us; dropping the returnedBoxfrees the node and runs its drop glue. We extract the element by value before the box drops.pub fn pop_front(&mut self) -> Option<T> { unsafe { self.front.map(|node| { let boxed = Box::from_raw(node.as_ptr()); self.front = boxed.back; match self.front { Some(new) => (*new.as_ptr()).front = None, None => self.back = None, } self.len -= 1; boxed.elem }) } } -
push_backandpop_backare the same code withfront/backswapped throughout. In a real implementation you write them out explicitly — macros that generate them are a worse experience for everyone reading the code than four eight-line functions.pub fn push_back(&mut self, elem: T) { unsafe { let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node { back: None, front: self.back, elem, }))); match self.back { Some(old) => (*old.as_ptr()).back = Some(new), None => self.front = Some(new), } self.back = Some(new); self.len += 1; } } pub fn pop_back(&mut self) -> Option<T> { unsafe { self.back.map(|node| { let boxed = Box::from_raw(node.as_ptr()); self.back = boxed.front; match self.back { Some(new) => (*new.as_ptr()).back = None, None => self.front = None, } self.len -= 1; boxed.elem }) } } - Drop is the iterative form we wrote in Chapter 1, modernized:
while let Some(_) = self.pop_front() {}. Eachpop_frontreconstructs and drops one box, freeing one node. No recursion, constant stack.impl<T> Drop for LinkedList<T> { fn drop(&mut self) { while self.pop_front().is_some() {} } }