Flatten Nested List
Tutorial Video
Text description (accessibility)
This video demonstrates the "Flatten Nested List" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Recursive Data Structures, Enums. Flatten an arbitrarily nested list structure into a flat list. Key difference from OCaml: 1. **Enum vs variant**: Rust's `enum Node<T>` is the same concept as OCaml's variant type, but owns its data on the heap
Tutorial
The Problem
Flatten an arbitrarily nested list structure into a flat list.
Flattening nested lists appears in JSON document processing (flatten nested arrays), compiler AST processing (flatten nested expression lists), and file system traversal (flatten directory trees). The problem requires a recursive data type — a list where each element is either a value or another list — and structural recursion over that type. This is the simplest non-trivial example of processing an algebraic data type.
🎯 Learning Outcomes
type 'a node with Rust's enum Node<T>flat_map provides a declarative recursive solution🦀 The Rust Way
Three approaches:
flat_map + pattern matching — clean but allocates intermediate Vecsinto_iter, zero cloning — most efficient when data isn't reusedCode Example
pub fn flatten<T: Clone>(list: &[Node<T>]) -> Vec<T> {
list.iter()
.flat_map(|node| match node {
Node::One(x) => vec![x.clone()],
Node::Many(xs) => flatten(xs),
})
.collect()
}Key Differences
enum Node<T> is the same concept as OCaml's variant type, but owns its data on the heapflatten_owned) avoids all cloning by taking ownership — impossible in GC languages where sharing is implicitT: Clone to extract values; OCaml copies freely under GCVec<Node<T>> is a contiguous heap buffer; OCaml's list is a chain of heap-allocated cons cellsBox for recursion:** Rust's Node::Many(Vec<Node<T>>) uses a Vec (not Box<Node>) because a Vec<Node> has a known size (pointer + length + capacity). OCaml's type 'a node = One of 'a | Many of 'a node list uses a GC-managed list directly.Clone bound:** flatten<T: Clone> requires cloning values extracted from the borrowed &Node<T>. OCaml's GC shares values without cloning.flatten_stack uses a manual Vec<&Node<T>> stack to avoid actual recursion, ensuring stack safety for deeply nested inputs. OCaml's TCO handles this automatically with tail-recursive accumulator style.Node::One(T) owns its value. Flattening requires moving or cloning values out of the tree — Rust forces this to be explicit.OCaml Approach
Defines a recursive variant type 'a node = One of 'a | Many of 'a node list, then uses a tail-recursive helper with an accumulator to flatten. The GC handles all intermediate allocations.
Full Source
#![allow(clippy::all)]
//! # Flatten Nested List
//! OCaml 99 Problems #7 — Flatten an arbitrarily nested list structure.
/// Mirrors OCaml's `type 'a node = One of 'a | Many of 'a node list`.
/// Rust enum with owned data — no GC, explicit ownership.
#[derive(Debug, PartialEq, Clone)]
pub enum Node<T> {
One(T),
Many(Vec<Node<T>>),
}
/// Idiomatic Rust: use `flat_map` with recursive flattening.
/// Requires `Clone` because we extract values from a borrowed structure.
pub fn flatten<T: Clone>(list: &[Node<T>]) -> Vec<T> {
list.iter()
.flat_map(|node| match node {
Node::One(x) => vec![x.clone()],
Node::Many(xs) => flatten(xs),
})
.collect()
}
/// Functional style: tail-recursive with accumulator (mirrors OCaml's `aux acc`).
/// Uses a stack to avoid actual recursion — Rust doesn't guarantee TCO.
pub fn flatten_stack<T: Clone>(list: &[Node<T>]) -> Vec<T> {
let mut result = Vec::new();
// Explicit stack: process nodes right-to-left so result is in order
let mut stack: Vec<&Node<T>> = list.iter().rev().collect();
while let Some(node) = stack.pop() {
match node {
Node::One(x) => result.push(x.clone()),
Node::Many(xs) => {
for child in xs.iter().rev() {
stack.push(child);
}
}
}
}
result
}
/// Consuming version: takes ownership, no cloning needed.
/// This is the most efficient Rust approach when you own the data.
pub fn flatten_owned<T>(list: Vec<Node<T>>) -> Vec<T> {
let mut result = Vec::new();
let mut stack: Vec<Node<T>> = list.into_iter().rev().collect();
while let Some(node) = stack.pop() {
match node {
Node::One(x) => result.push(x),
Node::Many(xs) => {
for child in xs.into_iter().rev() {
stack.push(child);
}
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use Node::*;
#[test]
fn test_nested() {
let input = vec![
One(1),
Many(vec![One(2), Many(vec![One(3), One(4)])]),
One(5),
];
assert_eq!(flatten(&input), vec![1, 2, 3, 4, 5]);
assert_eq!(flatten_stack(&input), vec![1, 2, 3, 4, 5]);
assert_eq!(flatten_owned(input), vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_empty() {
assert_eq!(flatten::<i32>(&[]), Vec::<i32>::new());
assert_eq!(flatten_stack::<i32>(&[]), Vec::<i32>::new());
assert_eq!(flatten_owned::<i32>(vec![]), Vec::<i32>::new());
}
#[test]
fn test_single() {
assert_eq!(flatten(&[One(42)]), vec![42]);
}
#[test]
fn test_flat_list() {
let input = vec![One(1), One(2), One(3)];
assert_eq!(flatten(&input), vec![1, 2, 3]);
}
#[test]
fn test_deeply_nested() {
let input = vec![Many(vec![Many(vec![Many(vec![One(1)])])])];
assert_eq!(flatten(&input), vec![1]);
}
#[test]
fn test_empty_many() {
let input: Vec<Node<i32>> = vec![Many(vec![]), One(1)];
assert_eq!(flatten(&input), vec![1]);
}
}#[cfg(test)]
mod tests {
use super::*;
use Node::*;
#[test]
fn test_nested() {
let input = vec![
One(1),
Many(vec![One(2), Many(vec![One(3), One(4)])]),
One(5),
];
assert_eq!(flatten(&input), vec![1, 2, 3, 4, 5]);
assert_eq!(flatten_stack(&input), vec![1, 2, 3, 4, 5]);
assert_eq!(flatten_owned(input), vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_empty() {
assert_eq!(flatten::<i32>(&[]), Vec::<i32>::new());
assert_eq!(flatten_stack::<i32>(&[]), Vec::<i32>::new());
assert_eq!(flatten_owned::<i32>(vec![]), Vec::<i32>::new());
}
#[test]
fn test_single() {
assert_eq!(flatten(&[One(42)]), vec![42]);
}
#[test]
fn test_flat_list() {
let input = vec![One(1), One(2), One(3)];
assert_eq!(flatten(&input), vec![1, 2, 3]);
}
#[test]
fn test_deeply_nested() {
let input = vec![Many(vec![Many(vec![Many(vec![One(1)])])])];
assert_eq!(flatten(&input), vec![1]);
}
#[test]
fn test_empty_many() {
let input: Vec<Node<i32>> = vec![Many(vec![]), One(1)];
assert_eq!(flatten(&input), vec![1]);
}
}
Deep Comparison
Comparison: Flatten Nested List
OCaml — Recursive with accumulator
type 'a node = One of 'a | Many of 'a node list
let flatten lst =
let rec aux acc = function
| [] -> acc
| One x :: t -> aux (x :: acc) t
| Many xs :: t -> aux (aux acc xs) t
in
List.rev (aux [] lst)
Rust — Idiomatic (flat_map)
pub fn flatten<T: Clone>(list: &[Node<T>]) -> Vec<T> {
list.iter()
.flat_map(|node| match node {
Node::One(x) => vec![x.clone()],
Node::Many(xs) => flatten(xs),
})
.collect()
}
Rust — Stack-based (no recursion)
pub fn flatten_stack<T: Clone>(list: &[Node<T>]) -> Vec<T> {
let mut result = Vec::new();
let mut stack: Vec<&Node<T>> = list.iter().rev().collect();
while let Some(node) = stack.pop() {
match node {
Node::One(x) => result.push(x.clone()),
Node::Many(xs) => {
for child in xs.iter().rev() {
stack.push(child);
}
}
}
}
result
}
Comparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Type definition | type 'a node = One of 'a \| Many of 'a node list | enum Node<T> { One(T), Many(Vec<Node<T>>) } |
| Recursion | Tail-recursive with accumulator | Stack-based iteration (safer) |
| Memory | GC-managed cons cells | Owned Vec on heap |
| Copying | Implicit (GC) | Explicit Clone trait bound |
| Ownership | Shared by default | Must choose: borrow (&) or consume |
Takeaways
flat_map gives the most readable recursive solution but allocates intermediate vectorsflatten_owned) is uniquely Rust — it's impossible in GC languages where data is always sharedExercises
flatten_depth that flattens a nested list structure only up to a specified depth d, leaving deeper nesting intact.flatten_unique that flattens a list-of-lists and removes duplicates, preserving the first occurrence of each element.flatten_with that flattens a nested structure while applying a transformation function to each leaf element before collecting results.