๐Ÿฆ€ Functional Rust

1035: Doubly-Linked List

Difficulty: Advanced Category: Data Structures Concept: Doubly-linked list using `Rc<RefCell<Node>>` for safe shared mutable references Key Insight: Doubly-linked lists need shared ownership (two nodes point to each other) โ€” Rust requires `Rc<RefCell<T>>` to express this safely, while OCaml uses either mutable records or zippers.
// 1035: Doubly-Linked List โ€” Rc<RefCell<Node>> Approach
// Safe doubly-linked list using reference counting + interior mutability

use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;

type Link<T> = Option<Rc<RefCell<DNode<T>>>>;

struct DNode<T> {
    value: T,
    prev: Link<T>,
    next: Link<T>,
}

struct DoublyLinkedList<T> {
    head: Link<T>,
    tail: Link<T>,
    len: usize,
}

impl<T> DoublyLinkedList<T> {
    fn new() -> Self {
        DoublyLinkedList {
            head: None,
            tail: None,
            len: 0,
        }
    }

    fn push_back(&mut self, value: T) {
        let new_node = Rc::new(RefCell::new(DNode {
            value,
            prev: self.tail.clone(),
            next: None,
        }));

        match self.tail.take() {
            Some(old_tail) => {
                old_tail.borrow_mut().next = Some(new_node.clone());
            }
            None => {
                self.head = Some(new_node.clone());
            }
        }
        self.tail = Some(new_node);
        self.len += 1;
    }

    fn push_front(&mut self, value: T) {
        let new_node = Rc::new(RefCell::new(DNode {
            value,
            prev: None,
            next: self.head.clone(),
        }));

        match self.head.take() {
            Some(old_head) => {
                old_head.borrow_mut().prev = Some(new_node.clone());
            }
            None => {
                self.tail = Some(new_node.clone());
            }
        }
        self.head = Some(new_node);
        self.len += 1;
    }

    fn pop_front(&mut self) -> Option<T>
    where
        T: Default,
    {
        self.head.take().map(|old_head| {
            let mut old_head_ref = old_head.borrow_mut();
            match old_head_ref.next.take() {
                Some(new_head) => {
                    new_head.borrow_mut().prev = None;
                    self.head = Some(new_head);
                }
                None => {
                    self.tail = None;
                }
            }
            self.len -= 1;
            std::mem::take(&mut old_head_ref.value)
        })
    }

    fn pop_back(&mut self) -> Option<T>
    where
        T: Default,
    {
        self.tail.take().map(|old_tail| {
            let mut old_tail_ref = old_tail.borrow_mut();
            match old_tail_ref.prev.take() {
                Some(new_tail) => {
                    new_tail.borrow_mut().next = None;
                    self.tail = Some(new_tail);
                }
                None => {
                    self.head = None;
                }
            }
            self.len -= 1;
            std::mem::take(&mut old_tail_ref.value)
        })
    }

    fn to_vec(&self) -> Vec<T>
    where
        T: Clone,
    {
        let mut result = Vec::new();
        let mut current = self.head.clone();
        while let Some(node) = current {
            result.push(node.borrow().value.clone());
            current = node.borrow().next.clone();
        }
        result
    }

    fn len(&self) -> usize {
        self.len
    }
}

impl<T: fmt::Debug + Clone> fmt::Debug for DoublyLinkedList<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", self.to_vec())
    }
}

fn basic_operations() {
    let mut list: DoublyLinkedList<i32> = DoublyLinkedList::new();
    list.push_back(1);
    list.push_back(2);
    list.push_back(3);
    list.push_front(0);

    assert_eq!(list.to_vec(), vec![0, 1, 2, 3]);
    assert_eq!(list.len(), 4);

    assert_eq!(list.pop_front(), Some(0));
    assert_eq!(list.pop_back(), Some(3));
    assert_eq!(list.to_vec(), vec![1, 2]);
}

fn bidirectional_traversal() {
    let mut list: DoublyLinkedList<i32> = DoublyLinkedList::new();
    for i in 1..=5 {
        list.push_back(i);
    }

    // Forward traversal
    let forward = list.to_vec();
    assert_eq!(forward, vec![1, 2, 3, 4, 5]);

    // Backward traversal
    let mut backward = Vec::new();
    let mut current = list.tail.clone();
    while let Some(node) = current {
        backward.push(node.borrow().value);
        current = node.borrow().prev.clone();
    }
    assert_eq!(backward, vec![5, 4, 3, 2, 1]);
}

fn main() {
    basic_operations();
    bidirectional_traversal();
    println!("โœ“ All tests passed");
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_basic() { basic_operations(); }

    #[test]
    fn test_bidirectional() { bidirectional_traversal(); }

    #[test]
    fn test_empty() {
        let mut list: DoublyLinkedList<i32> = DoublyLinkedList::new();
        assert_eq!(list.pop_front(), None);
        assert_eq!(list.pop_back(), None);
        assert_eq!(list.len(), 0);
    }

    #[test]
    fn test_single() {
        let mut list: DoublyLinkedList<i32> = DoublyLinkedList::new();
        list.push_back(42);
        assert_eq!(list.pop_front(), Some(42));
        assert!(list.head.is_none());
        assert!(list.tail.is_none());
    }
}
(* 1035: Doubly-Linked List *)
(* OCaml: immutable doubly-linked lists are impractical *)
(* We use a zipper (functional approach) or mutable records *)

(* Approach 1: Zipper as functional doubly-linked list *)
type 'a zipper = {
  left: 'a list;   (* reversed left part *)
  focus: 'a;
  right: 'a list;
}

let of_list = function
  | [] -> None
  | x :: xs -> Some { left = []; focus = x; right = xs }

let move_right z =
  match z.right with
  | [] -> None
  | x :: xs -> Some { left = z.focus :: z.left; focus = x; right = xs }

let move_left z =
  match z.left with
  | [] -> None
  | x :: xs -> Some { left = xs; focus = x; right = z.focus :: z.right }

let to_list z =
  List.rev z.left @ [z.focus] @ z.right

let zipper_test () =
  let z = Option.get (of_list [1; 2; 3; 4; 5]) in
  assert (z.focus = 1);
  let z = Option.get (move_right z) in
  assert (z.focus = 2);
  let z = Option.get (move_right z) in
  assert (z.focus = 3);
  let z = Option.get (move_left z) in
  assert (z.focus = 2);
  assert (to_list z = [1; 2; 3; 4; 5])

(* Approach 2: Mutable doubly-linked using records *)
type 'a dnode = {
  mutable value: 'a;
  mutable prev: 'a dnode option;
  mutable next: 'a dnode option;
}

type 'a dlist = {
  mutable head: 'a dnode option;
  mutable tail: 'a dnode option;
  mutable length: int;
}

let create () = { head = None; tail = None; length = 0 }

let push_back dl v =
  let node = { value = v; prev = dl.tail; next = None } in
  (match dl.tail with
   | Some t -> t.next <- Some node
   | None -> dl.head <- Some node);
  dl.tail <- Some node;
  dl.length <- dl.length + 1

let push_front dl v =
  let node = { value = v; prev = None; next = dl.head } in
  (match dl.head with
   | Some h -> h.prev <- Some node
   | None -> dl.tail <- Some node);
  dl.head <- Some node;
  dl.length <- dl.length + 1

let to_list_forward dl =
  let rec go acc = function
    | None -> List.rev acc
    | Some n -> go (n.value :: acc) n.next
  in
  go [] dl.head

let mutable_dlist_test () =
  let dl = create () in
  push_back dl 1;
  push_back dl 2;
  push_back dl 3;
  push_front dl 0;
  assert (to_list_forward dl = [0; 1; 2; 3]);
  assert (dl.length = 4)

let () =
  zipper_test ();
  mutable_dlist_test ();
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Doubly-Linked List โ€” Comparison

Core Insight

Doubly-linked lists require bidirectional pointers โ€” each node is shared by its neighbors. In Rust, this violates single-ownership rules, requiring `Rc` (shared ownership) + `RefCell` (interior mutability). OCaml either uses mutable records (imperative) or zippers (functional alternative).

OCaml Approach

  • Mutable records: `mutable prev/next` fields with option types
  • GC handles cycles โ€” no reference counting needed
  • Zipper alternative: `{ left; focus; right }` for functional bidirectional access
  • Standard library has no doubly-linked list

Rust Approach

  • `Rc<RefCell<Node<T>>>` โ€” reference-counted cells with runtime borrow checking
  • `clone()` on `Rc` creates shared references
  • `borrow()` / `borrow_mut()` for access (panics if rules violated at runtime)
  • Must be careful to break cycles to avoid memory leaks
  • `std::collections::LinkedList` exists but is rarely recommended

Comparison Table

FeatureOCaml (mutable)Rust (`Rc<RefCell>`)
Shared ownershipGC handles it`Rc` reference counting
Interior mutability`mutable` keyword`RefCell` runtime checks
Cycle handlingGC collects cyclesMust break manually
Borrow checkingNone (runtime safe)Runtime via RefCell
ErgonomicsSimple mutationVerbose `.borrow_mut()`
RecommendationFine for imperativeUse `Vec`/`VecDeque` instead