17
Basics
No push/pop — those imply mutation. The persistent vocabulary is prepend(elem) and tail(), both of which take &self and return a brand-new List that shares structure with the old one. head() returns Option<&T> for read access. Every method is non-mutating; the borrow checker has no work to do.
-
newandprepend: build a node whosenextis a clone ofself.head.Rc::cloneof anOption<Rc<_>>is justself.head.clone()—Option::clonecallsRc::cloneon the inner handle, which is a refcount bump, not a deep copy.impl<T> List<T> { pub fn new() -> Self { List { head: None } } pub fn prepend(&self, elem: T) -> List<T> { List { head: Some(Rc::new(Node { elem, next: self.head.clone(), })), } } } -
tailreturns the rest of the list.self.head.as_ref()borrows theOption<Rc<Node<T>>>asOption<&Rc<Node<T>>>;.and_thenextractsnode.next.clone()if there's a head, orNoneotherwise. Net cost: at most one refcount bump.impl<T> List<T> { pub fn tail(&self) -> List<T> { List { head: self.head.as_ref().and_then(|node| node.next.clone()), } } pub fn head(&self) -> Option<&T> { self.head.as_ref().map(|node| &node.elem) } } - Notice every method takes
&self. There's no&mut selfanywhere — the data structure is immutable from the outside.prependandtailproduce values; the caller decides whether to bind them to a new name or shadow the old one. Sharing is free at the API level: clone the list itself (viaRc::cloneif you wrap it, or by re-runningprepend) and you have two persistent views.let list = List::new().prepend(1).prepend(2).prepend(3); // list: 3 -> 2 -> 1 -> Nil let shorter = list.tail(); // shorter: 2 -> 1 -> Nil // list is still 3 -> 2 -> 1 -> Nil; both lists alive. assert_eq!(list.head(), Some(&3)); assert_eq!(shorter.head(), Some(&2));