🦀 Functional Rust

076: GCD and LCM — Euclidean Algorithm

Difficulty: 2 Level: Beginner Recursive Euclidean GCD, derived LCM, and fold-based variants for sequences — pure math with `Copy` types.

The Problem This Solves

Finding the greatest common divisor and least common multiple are fundamental number theory operations — used in fraction reduction, scheduling (find a common period), cryptography, and anywhere you need to simplify ratios. The Euclidean algorithm is also the canonical example of a pure recursive algorithm with no ownership complexity: integers are `Copy`, no heap allocation, and the recursive structure maps 1:1 from OCaml to Rust with zero changes to the logic.

The Intuition

The Euclidean algorithm: `gcd(a, 0) = a`, `gcd(a, b) = gcd(b, a % b)`. That's it. Each call reduces the problem: `gcd(48, 18)` → `gcd(18, 12)` → `gcd(12, 6)` → `gcd(6, 0)` = 6. LCM follows from GCD: `lcm(a, b) = a / gcd(a, b) * b`. The division before multiplication avoids overflow — divide first to reduce the magnitude, then multiply. For a list: fold with `gcd` as the combining function. `gcd_list([12, 18, 24]) = gcd(gcd(12, 18), 24) = gcd(6, 24) = 6`.

How It Works in Rust

// Euclidean algorithm — integers are Copy, so ownership is trivial
pub fn gcd(a: u64, b: u64) -> u64 {
 if b == 0 { a } else { gcd(b, a % b) }
}

// LCM via GCD — divide first to prevent overflow
pub fn lcm(a: u64, b: u64) -> u64 {
 if a == 0 || b == 0 { 0 } else { a / gcd(a, b) * b }
}

// GCD of a slice — fold pattern: OCaml's List.fold_left gcd 0
pub fn gcd_list(nums: &[u64]) -> u64 {
 nums.iter().copied().reduce(gcd).unwrap_or(0)
 //          ^^^^^^ Copy out each &u64 so we pass u64 to gcd directly
 //                              ^^^^^^ reduce = fold without initial value
}

// Accept any iterable — more flexible signature
pub fn gcd_iter(nums: impl IntoIterator<Item = u64>) -> u64 {
 nums.into_iter().reduce(gcd).unwrap_or(0)
}

// LCM of a list
pub fn lcm_list(nums: &[u64]) -> u64 {
 nums.iter().copied().reduce(lcm).unwrap_or(0)
}
`.copied()` on an iterator of `&u64` turns `Option<&u64>` into `Option<u64>` — necessary because `gcd` takes `u64`, not `&u64`. With `Copy` types, `.copied()` is idiomatic and free. `.reduce()` is like `.fold()` but uses the first element as the initial accumulator — appropriate when there's no sensible default (what's `gcd` of an empty list? We use 0 as a convention).

What This Unlocks

Key Differences

ConceptOCamlRust
Recursive GCD`let rec gcd a b = if b = 0 then a else gcd b (a mod b)``fn gcd(a: u64, b: u64) -> u64 { if b == 0 { a } else { gcd(b, a % b) } }`
Fold over list`List.fold_left gcd 0``.iter().copied().reduce(gcd)`
Ownership costNone (GC)None (Copy types)
Overflow guardManual`a / gcd(a, b) * b` pattern
Empty case`0` or exception`.unwrap_or(0)`
/// GCD using Euclidean algorithm
///
/// Ownership insight: All values are Copy (integers), so ownership
/// is trivial here. The recursive structure mirrors OCaml exactly.
pub fn gcd(a: u64, b: u64) -> u64 {
    if b == 0 { a } else { gcd(b, a % b) }
}

/// LCM using GCD
pub fn lcm(a: u64, b: u64) -> u64 {
    if a == 0 || b == 0 { 0 } else { a / gcd(a, b) * b }
}

/// GCD of a slice — uses fold pattern like OCaml List.fold_left
/// Ownership: slice is borrowed, no allocation needed
pub fn gcd_list(nums: &[u64]) -> u64 {
    nums.iter().copied().reduce(gcd).unwrap_or(0)
}

/// Iterator-based GCD — more idiomatic Rust
pub fn gcd_iter(nums: impl IntoIterator<Item = u64>) -> u64 {
    nums.into_iter().reduce(gcd).unwrap_or(0)
}

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

    #[test]
    fn test_gcd_basic() {
        assert_eq!(gcd(48, 18), 6);
        assert_eq!(gcd(100, 75), 25);
    }

    #[test]
    fn test_gcd_zero() {
        assert_eq!(gcd(5, 0), 5);
        assert_eq!(gcd(0, 5), 5);
    }

    #[test]
    fn test_lcm() {
        assert_eq!(lcm(12, 18), 36);
        assert_eq!(lcm(0, 5), 0);
    }

    #[test]
    fn test_gcd_list() {
        assert_eq!(gcd_list(&[48, 36, 60, 12]), 12);
        assert_eq!(gcd_list(&[]), 0);
    }

    #[test]
    fn test_gcd_iter() {
        assert_eq!(gcd_iter(vec![48, 36, 60, 12]), 12);
    }
}

fn main() {
    println!("{:?}", gcd(48, 18), 6);
    println!("{:?}", gcd(100, 75), 25);
    println!("{:?}", gcd(5, 0), 5);
}
(* GCD and LCM — Euclidean Algorithm *)

(* Version 1: Basic recursive *)
let rec gcd a b = if b = 0 then a else gcd b (a mod b)
let lcm a b = if a = 0 || b = 0 then 0 else abs (a * b) / gcd a b

(* Version 2: GCD over a list using fold *)
let gcd_list = function
  | [] -> 0
  | h :: t -> List.fold_left gcd h t

let () =
  assert (gcd 48 18 = 6);
  assert (lcm 12 18 = 36);
  assert (gcd_list [48; 36; 60; 12] = 12);
  Printf.printf "gcd(48,18) = %d\n" (gcd 48 18);
  Printf.printf "lcm(12,18) = %d\n" (lcm 12 18)

📊 Detailed Comparison

GCD and LCM — Comparison

Core Insight

The Euclidean algorithm is a pure recursive function on integers. Since integers are `Copy` in Rust, the translation is nearly identical — no ownership complications.

OCaml Approach

  • `let rec gcd a b = if b = 0 then a else gcd b (a mod b)` — concise one-liner
  • `List.fold_left gcd h t` — fold over list with GCD as combining function
  • Polymorphic integers (but typically `int`)

Rust Approach

  • Same recursive structure with explicit `u64` type
  • `iter().copied().reduce(gcd)` mirrors `fold_left`
  • Can also accept `impl IntoIterator` for generic input

Comparison Table

AspectOCamlRust
Recursion`let rec`plain `fn` (no keyword needed)
Modulo`mod` (operator)`%` (operator)
Fold`List.fold_left``.iter().reduce()`
Integer type`int` (63-bit)`u64` (explicit)
OverflowSilent wraparoundPanic in debug, wrap in release

Learner Notes

  • Rust requires explicit numeric types — no polymorphic `int`
  • `reduce` vs `fold`: reduce uses first element as initial value
  • For integers, ownership is trivial — everything is Copy
  • The `a / gcd(a,b) b` form avoids overflow vs `a b / gcd(a,b)`