15
Final Code The complete module: generic over T, using Option<Box<Node<T>>>, with peek/peek_mut, IntoIter, Iter, and IterMut. Hand-written Drop carries over from Chapter 1. This is what a typical safe Rust collection looks like before you start optimizing.
01
Read it top to bottom. Every method is now a one- to three-line operation on Option, Box, or a pointer cursor. There is no unsafe, no mem::replace, no custom enum.
pub struct List<T> {
head: Link<T>,
}
type Link<T> = Option<Box<Node<T>>>;
struct Node<T> {
elem: T,
next: Link<T>,
}
impl<T> List<T> {
pub fn new() -> Self {
List { head: None }
}
pub fn push(&mut self, elem: T) {
let new_node = Box::new(Node {
elem,
next: self.head.take(),
});
self.head = Some(new_node);
}
pub fn pop(&mut self) -> Option<T> {
self.head.take().map(|node| {
self.head = node.next;
node.elem
})
}
pub fn peek(&self) -> Option<&T> {
self.head.as_ref().map(|node| &node.elem)
}
pub fn peek_mut(&mut self) -> Option<&mut T> {
self.head.as_mut().map(|node| &mut node.elem)
}
pub fn into_iter(self) -> IntoIter<T> {
IntoIter(self)
}
pub fn iter(&self) -> Iter<'_, T> {
Iter { next: self.head.as_deref() }
}
pub fn iter_mut(&mut self) -> IterMut<'_, T> {
IterMut { next: self.head.as_deref_mut() }
}
}
impl<T> Drop for List<T> {
fn drop(&mut self) {
let mut cur = self.head.take();
while let Some(mut node) = cur {
cur = node.next.take();
}
}
}
pub struct IntoIter<T>(List<T>);
impl<T> Iterator for IntoIter<T> {
type Item = T;
fn next(&mut self) -> Option<T> { self.0.pop() }
}
pub struct Iter<'a, T> {
next: Option<&'a Node<T>>,
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<&'a T> {
self.next.map(|node| {
self.next = node.next.as_deref();
&node.elem
})
}
}
pub struct IterMut<'a, T> {
next: Option<&'a mut Node<T>>,
}
impl<'a, T> Iterator for IterMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<&'a mut T> {
self.next.take().map(|node| {
self.next = node.next.as_deref_mut();
&mut node.elem
})
}
} TL;DR
A generic singly-linked stack with peek, peek_mut, and all three iterator flavors — built on Option, Box, and lifetimes, with no unsafe. This is the baseline a real Rust collection looks like.
End of Chapter II
Three perspectives on what you just read Swapping the hand-rolled Link enum for Option<Box<Node<T>>> deletes a redundant typedef: the standard library already encodes the null-or-owned-pointer discriminant, and the niche optimization keeps it one machine word. Option::take is roughly the borrow-checker-safe version of swapping a pointer with NULL before freeing it, except the type system refuses to let you forget. Generics are monomorphized like C++ templates, so push and pop cost nothing extra. The three iterator types fall out as ownership variants: IntoIter consumes, Iter<'a, T> hands out &'a T, IterMut<'a, T> hands out &'a mut T. The lifetime is conceptually __restrict__ carried in the type.
# Py-track For readers from Ruby, Python, JavaScript Option::take does roughly what x, self.head = self.head, None does in Python, except the compiler refuses the version where you forget the reassignment. Making the list generic over T is the static-typed analogue of duck typing: declare the slot once with <T> and the compiler stamps out a specialized version per element type. peek and peek_mut mirror reading an attribute versus assigning through it, but they get distinct names because the borrow checker tracks them differently. The three iterators map to the three things a for loop could mean: empty the container, look without touching, or mutate in place. Lifetimes say the iterator is a view that may not outlive the list.
By the end of this chapter the list can hold any kind of value, not just whole numbers, because you taught it a placeholder type called T that gets filled in whenever someone uses the list. You added two ways to peek at the front item: one that only looks, and one that lets you change it in place. You also wrote three ways to walk through the list, because walking can mean three things: emptying as you go, looking at each item, or editing each item. The word lifetime showed up, and in everyday terms it is a promise that a borrowed value will not be used after the thing it came from is gone. Option::take grabs whatever was in a slot and leaves an empty marker behind.