28
Layout
Push to the back is the operation that breaks safe ownership. The head owns a Box<Node> chain; the tail needs a second pointer into the last node of that same chain. Two Boxes to the same allocation is a double-free. The escape hatch is *mut Node<T> — a raw pointer that doesn't own anything and doesn't participate in borrow checking.
- First the obvious-but-wrong layout: head and tail both as
Option<Box<Node>>. The compiler accepts the type but the semantics are broken — once you push three items, the secondBoxaliases memory the first one already owns. On drop you free the same allocation twice.// broken — do not write this pub struct List<T> { head: Link<T>, tail: Link<T>, // aliases head's chain — double-free on drop } type Link<T> = Option<Box<Node<T>>>; struct Node<T> { elem: T, next: Link<T>, } - The fix is a raw pointer.
*mut Node<T>is exactly whatNode*is in C: an address, no provenance check at runtime, no ownership semantics, noDrop. The head still owns the chain viaBox; the tail is a borrow-the-address-and-trust-me back-channel into the same memory. Initialize it tonull_mut()and update it on everypush_back.use std::ptr; pub struct List<T> { head: Link<T>, tail: *mut Node<T>, // raw pointer — does not own } 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, tail: ptr::null_mut() } } }