Learn Rust With Entirely Too Many Linked Lists
10

Generic

Our list only holds i32. Generics in Rust are like C++ templates: a type parameter T that the compiler stamps out a fresh copy of the code for at each use site. Monomorphization, same as std::vector<T>.

  1. Add <T> to every type and impl. The struct, the node, the link alias, the impl block header. The body of each method already speaks in terms of the field types, so almost nothing else changes.
    pub struct List<T> {
        head: Link<T>,
    }
    
    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 }
        }
    }
  2. push and pop need T plumbed through their signatures. Note the impl<T> on the block — that introduces the parameter for everything inside, and Self already means List<T>.
    impl<T> List<T> {
        pub fn push(&mut self, elem: T) {
            let new_node = Box::new(Node {
                elem,
                next: self.head.take(),
            });
            self.head = Some(new_node);
        }
    
        pub fn pop(&mut self) -> Option<T> {
            self.head.take().map(|node| {
                self.head = node.next;
                node.elem
            })
        }
    }
  3. Generics aren't free at the binary level — List<i32> and List<String> produce two separate copies of every method. That's monomorphization, and it's why Rust generics are zero-overhead at runtime but can balloon code size if you go wild.
    let mut a: List<i32> = List::new();
    let mut b: List<String> = List::new();
    a.push(1);
    b.push("hello".to_string());