๐Ÿฆ€ Functional Rust

029: Binary Tree

Difficulty: โญ Level: Foundations Define a binary tree type in Rust and implement basic operations: size and height.

The Problem This Solves

Almost every non-trivial program eventually needs a tree. File systems are trees. HTML is a tree. Abstract syntax trees power every compiler. If you can model and traverse a binary tree, you have the foundation for all of them. In Python or Java you'd write a class with `left` and `right` fields that can be `None` or `null`. You'd construct it step by step, and nothing stops you from building a half-broken tree โ€” a node with a left child but no right field set. You have to be disciplined to keep things consistent. In Rust, we use an `enum`: a tree node is either a `Leaf` (empty, no data) or a `Node(left, value, right)`. The type system enforces the shape. You can't have a half-constructed tree โ€” every `Node` always has both subtrees, and every `Leaf` has none. This is algebraic data types (ADTs) in action.

The Intuition

Think of a binary tree like a Russian nesting doll. A `Leaf` is the smallest doll โ€” nothing inside. A `Node` is a bigger doll containing a value and exactly two more dolls (which can themselves be big or small). In Python you might write:
class Tree:
 def __init__(self, val, left=None, right=None):
     self.val = val
     self.left = left
     self.right = right
In Rust we express this as an enum:
enum Tree<T> {
 Leaf,
 Node(Box<Tree<T>>, T, Box<Tree<T>>),
}
The `<T>` makes it generic โ€” a `Tree<char>` holds characters, a `Tree<i32>` holds integers. Why `Box<T>`? Rust needs to know the size of every type at compile time. A `Tree<T>` that contains another `Tree<T>` would be infinitely large on the stack. `Box<Tree<T>>` stores a pointer to the subtree on the heap โ€” known size (one pointer), any depth tree. That's all it is.

How It Works in Rust

#[derive(Debug, PartialEq, Clone)]
enum Tree<T> {
 Leaf,
 Node(Box<Tree<T>>, T, Box<Tree<T>>),
}

impl<T: Clone> Tree<T> {
 // Convenience constructors โ€” wrap Box::new for you
 fn leaf() -> Self { Tree::Leaf }
 fn node(left: Tree<T>, val: T, right: Tree<T>) -> Self {
     Tree::Node(Box::new(left), val, Box::new(right))
 }

 // Recursion + pattern matching: each arm handles one case
 fn size(&self) -> usize {
     match self {
         Tree::Leaf => 0,
         Tree::Node(l, _, r) => 1 + l.size() + r.size(),
     }
 }

 fn height(&self) -> usize {
     match self {
         Tree::Leaf => 0,
         Tree::Node(l, _, r) => 1 + l.height().max(r.height()),
     }
 }
}

// Build a tree:
//        a
//       / \
//      b   c
let t = Tree::node(
 Tree::node(Tree::leaf(), 'b', Tree::leaf()),
 'a',
 Tree::node(Tree::leaf(), 'c', Tree::leaf()),
);
println!("{}", t.size());   // 3
println!("{}", t.height()); // 2
The `_` in `Node(l, _, r)` ignores the value โ€” we only need left and right for size/height. Rust will warn you if you forget to use a bound variable, so `_` signals "intentionally unused."

What This Unlocks

Key Differences

ConceptOCamlRust
Tree type`type 'a tree = Leaf \Node of 'a tree 'a 'a tree``enum Tree<T> { Leaf, Node(Box<Tree<T>>, T, Box<Tree<T>>) }`
Recursive heap allocationAutomatic (GC)Explicit `Box::new(...)`
Pattern matching`match t with \Leaf -> ... \Node(l,v,r) -> ...``match t { Tree::Leaf => ..., Tree::Node(l,v,r) => ... }`
GenericsType parameter `'a`Type parameter `T` with trait bounds
// Binary Tree โ€” 99 Problems #29
// Define a binary tree type and basic operations.
// In OCaml: type 'a tree = Leaf | Node of 'a tree * 'a * 'a tree

#[derive(Debug, PartialEq, Clone)]
enum Tree<T> {
    Leaf,
    Node(Box<Tree<T>>, T, Box<Tree<T>>),
}

impl<T: Clone> Tree<T> {
    fn leaf() -> Self {
        Tree::Leaf
    }

    fn node(left: Tree<T>, val: T, right: Tree<T>) -> Self {
        Tree::Node(Box::new(left), val, Box::new(right))
    }

    fn is_leaf(&self) -> bool {
        matches!(self, Tree::Leaf)
    }

    fn size(&self) -> usize {
        match self {
            Tree::Leaf => 0,
            Tree::Node(l, _, r) => 1 + l.size() + r.size(),
        }
    }

    fn height(&self) -> usize {
        match self {
            Tree::Leaf => 0,
            Tree::Node(l, _, r) => 1 + l.height().max(r.height()),
        }
    }
}

/// A sample tree:
///        a
///       / \
///      b   c
///     / \
///    d   e
fn sample_tree() -> Tree<char> {
    Tree::node(
        Tree::node(
            Tree::node(Tree::leaf(), 'd', Tree::leaf()),
            'b',
            Tree::node(Tree::leaf(), 'e', Tree::leaf()),
        ),
        'a',
        Tree::node(Tree::leaf(), 'c', Tree::leaf()),
    )
}

fn main() {
    let t = sample_tree();
    println!("Tree: {:?}", t);
    println!("Size:   {}", t.size());
    println!("Height: {}", t.height());

    let leaf: Tree<i32> = Tree::leaf();
    println!("Leaf is_leaf: {}", leaf.is_leaf());
    println!("Tree is_leaf: {}", t.is_leaf());

    let single = Tree::node(Tree::leaf(), 42, Tree::leaf());
    println!("Single node size: {}", single.size());
    println!("Single node height: {}", single.height());
}

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

    #[test]
    fn test_leaf() {
        let t: Tree<i32> = Tree::leaf();
        assert_eq!(t.size(), 0);
        assert_eq!(t.height(), 0);
        assert!(t.is_leaf());
    }

    #[test]
    fn test_single_node() {
        let t = Tree::node(Tree::leaf(), 1, Tree::leaf());
        assert_eq!(t.size(), 1);
        assert_eq!(t.height(), 1);
        assert!(!t.is_leaf());
    }

    #[test]
    fn test_sample_tree() {
        let t = sample_tree();
        assert_eq!(t.size(), 5);
        assert_eq!(t.height(), 3);
    }

    #[test]
    fn test_right_skewed() {
        let t = Tree::node(
            Tree::leaf(),
            1,
            Tree::node(Tree::leaf(), 2, Tree::node(Tree::leaf(), 3, Tree::leaf())),
        );
        assert_eq!(t.size(), 3);
        assert_eq!(t.height(), 3);
    }
}
(* Binary Tree *)
(* OCaml 99 Problems #29 *)

(* Implementation for example 29 *)

(* Tests *)
let () =
  (* Add tests *)
  print_endline "โœ“ OCaml tests passed"