Learn Rust With Entirely Too Many Linked Lists
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.

  1. 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 second Box aliases 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>,
    }
  2. The fix is a raw pointer. *mut Node<T> is exactly what Node* is in C: an address, no provenance check at runtime, no ownership semantics, no Drop. The head still owns the chain via Box; the tail is a borrow-the-address-and-trust-me back-channel into the same memory. Initialize it to null_mut() and update it on every push_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() }
        }
    }