41
Boring Combinatorics
Now the mechanical accessors. front, front_mut, back, back_mut. Then three iterators: by-value (IntoIter), by-shared-ref (Iter), by-mutable-ref (IterMut). The pattern is identical to Chapter 2; the only difference is dereferencing NonNull<Node<T>> instead of pattern-matching on a safe enum.
-
front/backreturnOption<&T>. We turnOption<NonNull<Node<T>>>intoOption<&T>viamap(|node| unsafe { &(*node.as_ptr()).elem }). The_mutversions use&mutinstead. The lifetime of the returned reference is tied to&self(or&mut self) by elision — the compiler will not let it escape past the next mutation.impl<T> LinkedList<T> { pub fn front(&self) -> Option<&T> { unsafe { self.front.map(|n| &(*n.as_ptr()).elem) } } pub fn front_mut(&mut self) -> Option<&mut T> { unsafe { self.front.map(|n| &mut (*n.as_ptr()).elem) } } pub fn back(&self) -> Option<&T> { unsafe { self.back.map(|n| &(*n.as_ptr()).elem) } } pub fn back_mut(&mut self) -> Option<&mut T> { unsafe { self.back.map(|n| &mut (*n.as_ptr()).elem) } } } -
Iter<'a, T>walks shared references. Hold a current pointer and a remaining-count; each call tonextreadselem, advances alongback, decrements the count. The shared-borrow lifetime'aties the iterator's references to the original list.PhantomData<&'a Node<T>>carries it.pub struct Iter<'a, T> { front: Option<NonNull<Node<T>>>, back: Option<NonNull<Node<T>>>, len: usize, _marker: PhantomData<&'a Node<T>>, } impl<'a, T> Iterator for Iter<'a, T> { type Item = &'a T; fn next(&mut self) -> Option<&'a T> { if self.len == 0 { return None; } unsafe { self.front.map(|n| { self.len -= 1; self.front = (*n.as_ptr()).back; &(*n.as_ptr()).elem }) } } } impl<T> LinkedList<T> { pub fn iter(&self) -> Iter<'_, T> { Iter { front: self.front, back: self.back, len: self.len, _marker: PhantomData } } } -
IterMutis the same shape with&'a mut TandPhantomData<&'a mut Node<T>>. The reason this is sound — and not aliasing-violating — is that the iterator holds an exclusive borrow of the whole list, never hands out two references to the same node, and walks linearly. Miri verifies this; we'll run it in the testing section.pub struct IterMut<'a, T> { front: Option<NonNull<Node<T>>>, back: Option<NonNull<Node<T>>>, len: usize, _marker: PhantomData<&'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> { if self.len == 0 { return None; } unsafe { self.front.map(|n| { self.len -= 1; self.front = (*n.as_ptr()).back; &mut (*n.as_ptr()).elem }) } } } -
IntoIteris aLinkedList<T>with a shim. It holds the original list by value and delegatesnexttopop_front,next_backtopop_back. That gets usDoubleEndedIteratorfor free and ensures the destructor runs on whatever's left if iteration stops early.pub struct IntoIter<T> { list: LinkedList<T> } impl<T> Iterator for IntoIter<T> { type Item = T; fn next(&mut self) -> Option<T> { self.list.pop_front() } fn size_hint(&self) -> (usize, Option<usize>) { (self.list.len, Some(self.list.len)) } } impl<T> DoubleEndedIterator for IntoIter<T> { fn next_back(&mut self) -> Option<T> { self.list.pop_back() } } impl<T> IntoIterator for LinkedList<T> { type Item = T; type IntoIter = IntoIter<T>; fn into_iter(self) -> IntoIter<T> { IntoIter { list: self } } }