34
Layout + Basics Redux
Rewrite the layout with raw pointers throughout: head and tail are both *mut Node<T>. Allocate via Box::new then immediately Box::into_raw; deallocate via Box::from_raw and let the box drop. There are no Box-owned references in flight, so there's nothing for stacked borrows to pop.
- New layout. Both fields are
*mut Node<T>.Dropis now mandatory — withoutBox-owned children, the chain doesn't free itself.use std::ptr; pub struct List<T> { head: *mut Node<T>, tail: *mut Node<T>, } struct Node<T> { elem: T, next: *mut Node<T>, } impl<T> List<T> { pub fn new() -> Self { List { head: ptr::null_mut(), tail: ptr::null_mut() } } } -
push_back: heap-allocate viaBox::new, hand the address toBox::into_rawto dissolve theBox, then wire it into the chain. No reference ever exists to the new node.pub fn push_back(&mut self, elem: T) { unsafe { let new_tail = Box::into_raw(Box::new(Node { elem, next: ptr::null_mut(), })); if self.tail.is_null() { self.head = new_tail; } else { (*self.tail).next = new_tail; } self.tail = new_tail; } } -
pop_front: reconstitute aBoxfromself.headviaBox::from_raw, take its element, advanceself.head, and let theBoxdrop. If we just popped the last node, null outself.tail.pub fn pop_front(&mut self) -> Option<T> { unsafe { if self.head.is_null() { None } else { let head_box: Box<Node<T>> = Box::from_raw(self.head); self.head = head_box.next; if self.head.is_null() { self.tail = ptr::null_mut(); } Some(head_box.elem) } } } -
Dropwalks the chain by repeatedly popping. Eachpop_frontreconstitutes and drops one node; the loop terminates whenheadis null. For a list ofT: !Copynon-trivially-destructible elements,Box<Node<T>>::dropruns the innerT's destructor too —drop_in_placesemantics, paid for transparently by the box.impl<T> Drop for List<T> { fn drop(&mut self) { while self.pop_front().is_some() {} } } - Run Miri. Stacked borrows: clean. Tree borrows: clean. The discipline is paying for itself: there is exactly one path to each node (the chain of raw pointers), and the only references that ever exist are short-lived ones inside method bodies that don't outlive their
unsafeblock. 2024 edition note: prefer&raw mutover&mut x as *mut _if you ever need a one-off raw pointer to a local — it's the strict-provenance idiom Rust 2024 nudges you toward.$ cargo +nightly miri test running 4 tests test queue::tests::push_pop ... ok test queue::tests::peek ... ok test queue::tests::iter ... ok test queue::tests::drop_long ... ok test result: ok. 4 passed; 0 failed