ExamplesBy LevelBy TopicLearning Paths
086 Fundamental

086 — Space Age

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "086 — Space Age" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Given an age in seconds, calculate how old a person would be on each planet in the solar system, based on each planet's orbital period relative to Earth's year (31,557,600 seconds). Key difference from OCaml: | Aspect | Rust | OCaml |

Tutorial

The Problem

Given an age in seconds, calculate how old a person would be on each planet in the solar system, based on each planet's orbital period relative to Earth's year (31,557,600 seconds). Implement age_on(planet: Planet, seconds: f64) -> f64 using both a match-based orbital period table and a lookup-array alternative.

🎯 Learning Outcomes

  • • Use Copy enums to represent fixed domain sets (Planet) with no heap allocation
  • • Define a const array Planet::ALL for iterating over all variants
  • • Use match to return a compile-time float constant per variant
  • • Apply the formula: age = seconds / (earth_year * orbital_period) cleanly
  • • Compare Rust's match-based dispatch with OCaml's equivalent function match
  • • Understand when a data-driven lookup table is clearer than match arms
  • Code Example

    #![allow(clippy::all)]
    /// Space Age — Float Computation with Variants
    ///
    /// Ownership: Planet is Copy (enum with no data). All computations use f64 (Copy).
    
    #[derive(Debug, Clone, Copy)]
    pub enum Planet {
        Mercury,
        Venus,
        Earth,
        Mars,
        Jupiter,
        Saturn,
        Uranus,
        Neptune,
    }
    
    impl Planet {
        pub fn orbital_period(self) -> f64 {
            match self {
                Planet::Mercury => 0.2408467,
                Planet::Venus => 0.61519726,
                Planet::Earth => 1.0,
                Planet::Mars => 1.8808158,
                Planet::Jupiter => 11.862615,
                Planet::Saturn => 29.447498,
                Planet::Uranus => 84.016846,
                Planet::Neptune => 164.79132,
            }
        }
    
        pub const ALL: [Planet; 8] = [
            Planet::Mercury,
            Planet::Venus,
            Planet::Earth,
            Planet::Mars,
            Planet::Jupiter,
            Planet::Saturn,
            Planet::Uranus,
            Planet::Neptune,
        ];
    }
    
    const EARTH_YEAR_SECONDS: f64 = 31_557_600.0;
    
    pub fn age_on(planet: Planet, seconds: f64) -> f64 {
        seconds / (EARTH_YEAR_SECONDS * planet.orbital_period())
    }
    
    /// Version 2: Using a lookup table instead of match
    pub fn age_on_table(planet_index: usize, seconds: f64) -> f64 {
        const PERIODS: [f64; 8] = [
            0.2408467, 0.61519726, 1.0, 1.8808158, 11.862615, 29.447498, 84.016846, 164.79132,
        ];
        seconds / (EARTH_YEAR_SECONDS * PERIODS[planet_index])
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        fn approx(a: f64, b: f64) -> bool {
            (a - b).abs() < 0.01
        }
    
        #[test]
        fn test_earth() {
            assert!(approx(age_on(Planet::Earth, 1_000_000_000.0), 31.69));
        }
    
        #[test]
        fn test_mercury() {
            assert!(approx(age_on(Planet::Mercury, 1_000_000_000.0), 131.56));
        }
    
        #[test]
        fn test_neptune() {
            assert!(approx(age_on(Planet::Neptune, 1_000_000_000.0), 0.19));
        }
    
        #[test]
        fn test_all_planets() {
            for &p in &Planet::ALL {
                let age = age_on(p, EARTH_YEAR_SECONDS);
                assert!(approx(age, 1.0 / p.orbital_period()));
            }
        }
    
        #[test]
        fn test_table_version() {
            assert!(approx(age_on_table(0, 1_000_000_000.0), 131.56));
        }
    }

    Key Differences

    AspectRustOCaml
    Variant copy#[derive(Copy)]Value type by default
    Float ops*, / (same as integer)*., /. (distinct operators)
    Constantconst EARTH_YEAR_SECONDS: f64let earth_year_seconds = …
    All variantsconst ALL: [Planet; 8]Manual list or macro
    Dispatchmatch self { … }function \| Mercury -> …
    Code size~40 lines~15 lines

    The Planet::ALL constant is a useful pattern for any enum where you need to iterate over all variants. Without a derive macro like strum, Rust requires defining it manually — but as a const array it is zero-overhead and compile-time verified.

    OCaml Approach

    OCaml's version is nearly identical: a type planet variant, an orbital_period function using function, and age_on computing the quotient. The OCaml float operators are suffixed (/., *.), making the arithmetic explicit. There is no Copy concept — variants are value types by default. OCaml lacks a const array of all constructors, so exhaustive iteration would require a manually defined list.

    Full Source

    #![allow(clippy::all)]
    /// Space Age — Float Computation with Variants
    ///
    /// Ownership: Planet is Copy (enum with no data). All computations use f64 (Copy).
    
    #[derive(Debug, Clone, Copy)]
    pub enum Planet {
        Mercury,
        Venus,
        Earth,
        Mars,
        Jupiter,
        Saturn,
        Uranus,
        Neptune,
    }
    
    impl Planet {
        pub fn orbital_period(self) -> f64 {
            match self {
                Planet::Mercury => 0.2408467,
                Planet::Venus => 0.61519726,
                Planet::Earth => 1.0,
                Planet::Mars => 1.8808158,
                Planet::Jupiter => 11.862615,
                Planet::Saturn => 29.447498,
                Planet::Uranus => 84.016846,
                Planet::Neptune => 164.79132,
            }
        }
    
        pub const ALL: [Planet; 8] = [
            Planet::Mercury,
            Planet::Venus,
            Planet::Earth,
            Planet::Mars,
            Planet::Jupiter,
            Planet::Saturn,
            Planet::Uranus,
            Planet::Neptune,
        ];
    }
    
    const EARTH_YEAR_SECONDS: f64 = 31_557_600.0;
    
    pub fn age_on(planet: Planet, seconds: f64) -> f64 {
        seconds / (EARTH_YEAR_SECONDS * planet.orbital_period())
    }
    
    /// Version 2: Using a lookup table instead of match
    pub fn age_on_table(planet_index: usize, seconds: f64) -> f64 {
        const PERIODS: [f64; 8] = [
            0.2408467, 0.61519726, 1.0, 1.8808158, 11.862615, 29.447498, 84.016846, 164.79132,
        ];
        seconds / (EARTH_YEAR_SECONDS * PERIODS[planet_index])
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        fn approx(a: f64, b: f64) -> bool {
            (a - b).abs() < 0.01
        }
    
        #[test]
        fn test_earth() {
            assert!(approx(age_on(Planet::Earth, 1_000_000_000.0), 31.69));
        }
    
        #[test]
        fn test_mercury() {
            assert!(approx(age_on(Planet::Mercury, 1_000_000_000.0), 131.56));
        }
    
        #[test]
        fn test_neptune() {
            assert!(approx(age_on(Planet::Neptune, 1_000_000_000.0), 0.19));
        }
    
        #[test]
        fn test_all_planets() {
            for &p in &Planet::ALL {
                let age = age_on(p, EARTH_YEAR_SECONDS);
                assert!(approx(age, 1.0 / p.orbital_period()));
            }
        }
    
        #[test]
        fn test_table_version() {
            assert!(approx(age_on_table(0, 1_000_000_000.0), 131.56));
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        fn approx(a: f64, b: f64) -> bool {
            (a - b).abs() < 0.01
        }
    
        #[test]
        fn test_earth() {
            assert!(approx(age_on(Planet::Earth, 1_000_000_000.0), 31.69));
        }
    
        #[test]
        fn test_mercury() {
            assert!(approx(age_on(Planet::Mercury, 1_000_000_000.0), 131.56));
        }
    
        #[test]
        fn test_neptune() {
            assert!(approx(age_on(Planet::Neptune, 1_000_000_000.0), 0.19));
        }
    
        #[test]
        fn test_all_planets() {
            for &p in &Planet::ALL {
                let age = age_on(p, EARTH_YEAR_SECONDS);
                assert!(approx(age, 1.0 / p.orbital_period()));
            }
        }
    
        #[test]
        fn test_table_version() {
            assert!(approx(age_on_table(0, 1_000_000_000.0), 131.56));
        }
    }

    Deep Comparison

    Space Age — Comparison

    Core Insight

    Simple enum + pattern matching translates almost identically between OCaml and Rust. When all data is Copy (enums, floats), ownership is invisible. The languages converge on the same clean pattern.

    OCaml Approach

  • type planet = Mercury | Venus | ... — simple variant type
  • let orbital_period = function | Mercury -> 0.24... — pattern match function
  • /. for float division (separate operator from integer /)
  • • No method syntax — free functions
  • Rust Approach

  • enum Planet { Mercury, Venus, ... } with #[derive(Copy, Clone)]
  • impl Planet { fn orbital_period(self) -> f64 { match self { ... } } }
  • / for both int and float division
  • const ALL array for iteration over all variants
  • Comparison Table

    AspectOCamlRust
    Varianttype planet = Mercury \| ...enum Planet { Mercury, ... }
    Matchfunction \| pat -> ...match self { pat => ... }
    Float ops/. *./ *
    Constantslet x = 31557600.0const X: f64 = 31_557_600.0
    Iterate variantsManual listconst ALL array

    Learner Notes

  • • Rust has no separate float operators — type inference handles it
  • const in Rust is compile-time; OCaml let at module level is similar
  • • Rust numeric literals support _ separators: 31_557_600.0
  • • Both languages guarantee exhaustive matching — add a planet, compiler tells you
  • Exercises

  • Add a Display implementation for Planet that returns the planet name as a string.
  • Implement age_on_all(seconds: f64) -> [(Planet, f64); 8] that returns the age on every planet.
  • Find which planet's year is closest to a given number of Earth years using Planet::ALL.iter().min_by.
  • Add a Pluto variant (orbital period 247.92065 Earth years) even though it is not officially a planet, and verify the formula still works.
  • In OCaml, define all_planets : planet list and write age_on_all seconds using List.map.
  • Open Source Repos