Learn Rust With Entirely Too Many Linked Lists
23

Breaking

pop_front reverses push_front: take the head, splice the second node in as the new head, return the old element. The complication is unwrapping the element out from inside Rc<RefCell<Node<T>>> — there are three layers to peel.

  1. Take the head out with Option::take. Now we own one Rc<RefCell<Node<T>>>. Look at the head's next field — that's the new head. If it exists, null its prev; either way, splice it into self.head. If next is None, the list is now empty, so clear self.tail too.
    pub fn pop_front(&mut self) -> Option<T> {
        self.head.take().map(|old_head| {
            match old_head.borrow_mut().next.take() {
                Some(new_head) => {
                    new_head.borrow_mut().prev.take();
                    self.head = Some(new_head);
                }
                None => {
                    self.tail.take();
                }
            }
            // ... extract elem from old_head ...
            Rc::try_unwrap(old_head).ok().unwrap().into_inner().elem
        })
    }
  2. Rc::try_unwrap(rc) returns Ok(T) if the refcount is 1 (we are the unique owner) and Err(rc) otherwise — it can't safely move out if anyone else still holds a handle. Once we've severed the neighbours' pointers above, our old_head is the last Rc, so try_unwrap succeeds. Then RefCell::into_inner extracts the Node<T> from the cell, and .elem finally gets us the element.
    // the unwrap chain, expanded:
    let rc: Rc<RefCell<Node<T>>> = old_head;
    let cell: RefCell<Node<T>> = Rc::try_unwrap(rc).ok().unwrap();
    let node: Node<T>           = cell.into_inner();
    let elem: T                 = node.elem;
  3. Why .ok().unwrap() rather than .unwrap() directly? Rc::try_unwrap returns Result<T, Rc<T>> — the error carries the Rc back, which doesn't implement Debug unless T does. .ok() discards the error into an Option, which unwrap is happy with. The unwrap here would only fire on a logic bug; in correct code the refcount is always 1 at this point.
    let mut list = List::new();
    list.push_front(1);
    list.push_front(2);
    assert_eq!(list.pop_front(), Some(2));
    assert_eq!(list.pop_front(), Some(1));
    assert_eq!(list.pop_front(), None);