Learn Rust With Entirely Too Many Linked Lists
18

Drop

Same recursive-drop hazard as the Box list, with a wrinkle. Dropping an Rc<Node> only actually frees the node when the refcount hits zero — if another list still shares this tail, drop is a single decrement and we stop. But if we are the last owner of a long uniquely-held list, the auto-derived destructor recurses one frame per node and overflows.

  1. We can't use mem::replace to walk the list because we don't own the node — we only hold an Rc<Node>. The right tool is Rc::try_unwrap: if the refcount is exactly one, it consumes the Rc and gives us the inner Node by value; otherwise it hands the Rc back. We loop, unwrapping where we're the sole owner and stopping the moment a node is shared.
    use std::mem;
    
    impl<T> Drop for List<T> {
        fn drop(&mut self) {
            let mut head = self.head.take();
            while let Some(node) = head {
                if let Ok(mut node) = Rc::try_unwrap(node) {
                    // we were the sole owner — keep walking.
                    head = node.next.take();
                } else {
                    // someone else still holds this node; stop.
                    break;
                }
            }
        }
    }
  2. Trace it. head.take() pulls the head out as an Option<Rc<Node>>, leaving None behind. Rc::try_unwrap(node) succeeds (refcount was 1), yielding the Node by value. We take its next and loop. When try_unwrap fails, we break — the remaining nodes are someone else's problem, and dropping the Rc we couldn't unwrap is just one decrement.
    // uniquely-held: 3 -> 2 -> 1 -> Nil  (refcounts all 1)
    //   iter 1: try_unwrap node{3} -> Ok; head = Some(node{2})
    //   iter 2: try_unwrap node{2} -> Ok; head = Some(node{1})
    //   iter 3: try_unwrap node{1} -> Ok; head = None; loop exits.
    //
    // shared tail: 5 -> [2 -> 1] where [2 -> 1] is also held by another list
    //   iter 1: try_unwrap node{5} -> Ok; head = Some(node{2})
    //   iter 2: try_unwrap node{2} -> Err (refcount 2); break.
    //           Rc<Node{2}> drops here -> refcount goes 2 -> 1. Done.