48
Final Code The complete module: LinkedList<T>, the trait suite, three iterators, CursorMut, the lot. It's the same shape as std::collections::LinkedList and passes Miri. It's also a linked list, which means it's almost always the wrong choice in production.
01
The complete module — keep it open while reading the postscript below.
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::ptr::NonNull;
pub struct LinkedList<T> {
front: Option<NonNull<Node<T>>>,
back: Option<NonNull<Node<T>>>,
len: usize,
_marker: PhantomData<Box<Node<T>>>,
}
struct Node<T> {
front: Option<NonNull<Node<T>>>,
back: Option<NonNull<Node<T>>>,
elem: T,
}
impl<T> LinkedList<T> {
pub const fn new() -> Self {
LinkedList { front: None, back: None, len: 0, _marker: PhantomData }
}
pub fn len(&self) -> usize { self.len }
pub fn is_empty(&self) -> bool { self.len == 0 }
pub fn clear(&mut self) { while self.pop_front().is_some() {} }
pub fn push_front(&mut self, elem: T) { /* ... */ }
pub fn pop_front(&mut self) -> Option<T> { /* ... */ }
pub fn push_back(&mut self, elem: T) { /* ... */ }
pub fn pop_back(&mut self) -> Option<T> { /* ... */ }
pub fn front(&self) -> Option<&T> { /* ... */ unimplemented!() }
pub fn front_mut(&mut self) -> Option<&mut T> { unimplemented!() }
pub fn back(&self) -> Option<&T> { unimplemented!() }
pub fn back_mut(&mut self) -> Option<&mut T> { unimplemented!() }
pub fn iter(&self) -> Iter<'_, T> { unimplemented!() }
pub fn iter_mut(&mut self) -> IterMut<'_, T> { unimplemented!() }
pub fn cursor_mut(&mut self) -> CursorMut<'_, T> { unimplemented!() }
}
impl<T> Default for LinkedList<T> {
fn default() -> Self { LinkedList::new() }
}
impl<T> Drop for LinkedList<T> {
fn drop(&mut self) { while self.pop_front().is_some() {} }
}
unsafe impl<T: Send> Send for LinkedList<T> {}
unsafe impl<T: Sync> Sync for LinkedList<T> {}
// ... Iter, IterMut, IntoIter, CursorMut, Clone, Extend, FromIterator,
// Debug, PartialEq, Eq, PartialOrd, Ord, Hash, plus their Send/Sync impls,
// fill out the rest of the module exactly as shown across the chapter. 02
What's still wrong: linked lists thrash the cache (every node is its own allocation), pressure the allocator (one malloc per push), waste memory (two pointers of overhead per element), and don't vectorize (no contiguous storage). For nearly every workload, Vec<T> or VecDeque<T> outperforms a linked list by an order of magnitude. The places linked lists genuinely win — O(1) splice of arbitrary sublists, intrusive lists with hand-managed nodes — are rare in application code.
// the part where we admit defeat:
// for x in 0..1_000_000 { list.push_back(x); } // a million mallocs
// for x in 0..1_000_000 { vec.push(x); } // ~20 mallocs total TL;DR
We built a sound, generic, full-featured LinkedList<T> on NonNull raw pointers, with proper variance, dropck, panic safety, the std trait surface, and cursors. It's a faithful reimplementation of std::collections::LinkedList — and it's still slower than Vec for almost every realistic workload.
End of Chapter VI
Three perspectives on what you just read You have now built the canonical production deque in Rust, and the shape of the type tells the whole story: Option<NonNull<Node<T>>> for a nullable-but-niche-optimized owning pointer, paired with PhantomData<Box<Node<T>>> to declare ownership, covariance, and drop-check participation to the compiler. That pattern is the one to reach for whenever you have a raw heap allocation behind an opaque handle and need the borrow checker, the variance solver, and dropck to all behave as if you were holding a Box. Variance fell out cleanly because Rust's only subtyping is over lifetimes, so &'long T flowing into a &'short T slot is the entire feature, and your PhantomData choice decides whether your container preserves or inverts that flow. Panic safety forced the discipline that every public method has to leave the list in a coherent state even if T::clone unwinds mid-operation, which is the same invariant C++ exception-safe containers chase but enforced here by ownership rather than RAII guards alone. The full trait surface — Default, Clone, Extend, FromIterator, Debug, PartialEq, Ord, Hash, plus the unsafe Send/Sync impls you had to write by hand because raw pointers opt out by default — is what separates a teaching type from one you would actually ship. Cursors closed the gap iterators cannot: a stable position you can step in either direction and mutate around, which is the feature LinkedList exists for in the first place.
# Py-track For readers from Ruby, Python, JavaScript You just wrote, by hand, the kind of data structure that ships inside the runtime of every language you already know. Python's list, Ruby's Array, JavaScript's engine-level deques — they are all real production code with the same concerns you just worked through, the difference being that in those languages the code lives behind a C or C++ implementation barrier you never see. Here it was all exposed: every pointer, every drop, every trait you had to implement so that your LinkedList<T> could compose with the rest of the standard library the way a builtin would. You handled panic safety, which is the dynamic-language equivalent of asking what happens to your data structure if a user-defined __eq__ or <=> raises mid-operation — the answer has to be "the container is still valid," and you enforced that without a garbage collector to clean up after you. You implemented every trait a real collection needs — equality, ordering, hashing, cloning, iteration, debug printing, thread-safety markers — which is the same checklist the maintainers of your favorite stdlib went through, just usually written in a language with fewer guardrails than Rust gives you. Cursors are the part that does not exist in most dynamic stdlibs at all: a movable, editable position inside the list, which is genuinely hard to expose safely and is the reason a linked list earns its keep. You have now finished the production-grade portion of the book.
You just finished the longest and hardest chapter, and what you built is the kind of list that a real programming language would ship as part of its standard toolbox. Along the way you met a few ideas worth holding onto. Variance and subtyping sound abstract, but the everyday version is simple: if a piece of data is known to live for a long time, it is allowed to stand in wherever a shorter-lived piece of data was asked for, because outliving the requirement is never a problem. Panic safety is the rule that says any method on your list is allowed to fail partway through — maybe copying an item went wrong — but when the dust settles, the list itself still has to make sense and be safe to use or throw away. A cursor is a bookmark inside the list: a position you can move one step forward, one step backward, or use to insert and remove items right where you are standing, which is something a plain forward-only walk through the list cannot do. You also taught your list how to be compared, cloned, printed, hashed, and shared between threads, which is the full set of manners a data structure needs before the rest of the language will treat it as a first-class citizen. The rest of the book is shorter and more playful from here.