ExamplesBy LevelBy TopicLearning Paths
565 Fundamental

Tuple Struct Patterns

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Tuple Struct Patterns" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Tuple structs serve two roles: lightweight structs with positional fields, and newtypes — single-field wrappers that create type-distinct values. Key difference from OCaml: 1. **Syntax**: Rust `struct Meters(f64)` defines a tuple struct; OCaml `type meters = Meters of float` defines a single

Tutorial

The Problem

Tuple structs serve two roles: lightweight structs with positional fields, and newtypes — single-field wrappers that create type-distinct values. The newtype pattern (struct Meters(f64)) prevents accidentally passing Seconds where Meters is expected, even though both are f64 internally. Pattern matching on tuple structs extracts fields directly, either in match arms, let bindings, or function parameters. This is common in unit systems (NASA Mars Climate Orbiter crashed from a meters/feet confusion), domain modeling, and type-safe ID types.

🎯 Learning Outcomes

  • • How let Point(x, y) = p; destructures a tuple struct
  • • How fn f(Point(x, y): &Point) puts destructuring directly in function parameters
  • • How Meters(m) in a pattern extracts the inner value while discarding the wrapper
  • • How the newtype pattern (struct Meters(f64)) prevents type confusion at compile time
  • • Where tuple struct patterns appear: unit conversions, typed IDs, domain modeling
  • Code Example

    #![allow(clippy::all)]
    //! Tuple Struct Patterns
    //!
    //! Destructuring tuple structs and newtypes.
    
    pub struct Point(pub i32, pub i32);
    pub struct Color(pub u8, pub u8, pub u8);
    pub struct Meters(pub f64);
    pub struct Seconds(pub f64);
    
    /// Destructure tuple struct.
    pub fn get_coords(p: &Point) -> (i32, i32) {
        let Point(x, y) = p;
        (*x, *y)
    }
    
    /// Pattern in function params.
    pub fn add_points(Point(x1, y1): &Point, Point(x2, y2): &Point) -> Point {
        Point(x1 + x2, y1 + y2)
    }
    
    /// Newtype pattern.
    pub fn meters_to_feet(Meters(m): Meters) -> f64 {
        m * 3.28084
    }
    
    /// Match with tuple struct.
    pub fn describe_color(Color(r, g, b): &Color) -> &'static str {
        match (r, g, b) {
            (255, 0, 0) => "red",
            (0, 255, 0) => "green",
            (0, 0, 255) => "blue",
            (0, 0, 0) => "black",
            (255, 255, 255) => "white",
            _ => "other",
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_coords() {
            let p = Point(3, 4);
            assert_eq!(get_coords(&p), (3, 4));
        }
    
        #[test]
        fn test_add_points() {
            let p = add_points(&Point(1, 2), &Point(3, 4));
            assert_eq!((p.0, p.1), (4, 6));
        }
    
        #[test]
        fn test_meters() {
            let ft = meters_to_feet(Meters(1.0));
            assert!((ft - 3.28084).abs() < 0.001);
        }
    
        #[test]
        fn test_color() {
            assert_eq!(describe_color(&Color(255, 0, 0)), "red");
            assert_eq!(describe_color(&Color(128, 128, 128)), "other");
        }
    }

    Key Differences

  • Syntax: Rust struct Meters(f64) defines a tuple struct; OCaml type meters = Meters of float defines a single-constructor variant — both serve the newtype purpose.
  • Exhaustiveness: OCaml match on a single-constructor variant is always exhaustive; Rust let Meters(m) = val is irrefutable (always succeeds).
  • Type safety: Both prevent accidentally mixing Meters and Seconds — a compile error in both languages.
  • Transparency: Rust tuple struct fields can be pub or private; OCaml's abstraction is controlled via module signatures.
  • OCaml Approach

    OCaml achieves newtypes with abstract module signatures or single-constructor variants:

    type meters = Meters of float
    type seconds = Seconds of float
    let meters_to_feet (Meters m) = m *. 3.28084
    

    The Meters of float pattern is identical in concept to Rust's struct Meters(f64).

    Full Source

    #![allow(clippy::all)]
    //! Tuple Struct Patterns
    //!
    //! Destructuring tuple structs and newtypes.
    
    pub struct Point(pub i32, pub i32);
    pub struct Color(pub u8, pub u8, pub u8);
    pub struct Meters(pub f64);
    pub struct Seconds(pub f64);
    
    /// Destructure tuple struct.
    pub fn get_coords(p: &Point) -> (i32, i32) {
        let Point(x, y) = p;
        (*x, *y)
    }
    
    /// Pattern in function params.
    pub fn add_points(Point(x1, y1): &Point, Point(x2, y2): &Point) -> Point {
        Point(x1 + x2, y1 + y2)
    }
    
    /// Newtype pattern.
    pub fn meters_to_feet(Meters(m): Meters) -> f64 {
        m * 3.28084
    }
    
    /// Match with tuple struct.
    pub fn describe_color(Color(r, g, b): &Color) -> &'static str {
        match (r, g, b) {
            (255, 0, 0) => "red",
            (0, 255, 0) => "green",
            (0, 0, 255) => "blue",
            (0, 0, 0) => "black",
            (255, 255, 255) => "white",
            _ => "other",
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_coords() {
            let p = Point(3, 4);
            assert_eq!(get_coords(&p), (3, 4));
        }
    
        #[test]
        fn test_add_points() {
            let p = add_points(&Point(1, 2), &Point(3, 4));
            assert_eq!((p.0, p.1), (4, 6));
        }
    
        #[test]
        fn test_meters() {
            let ft = meters_to_feet(Meters(1.0));
            assert!((ft - 3.28084).abs() < 0.001);
        }
    
        #[test]
        fn test_color() {
            assert_eq!(describe_color(&Color(255, 0, 0)), "red");
            assert_eq!(describe_color(&Color(128, 128, 128)), "other");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_coords() {
            let p = Point(3, 4);
            assert_eq!(get_coords(&p), (3, 4));
        }
    
        #[test]
        fn test_add_points() {
            let p = add_points(&Point(1, 2), &Point(3, 4));
            assert_eq!((p.0, p.1), (4, 6));
        }
    
        #[test]
        fn test_meters() {
            let ft = meters_to_feet(Meters(1.0));
            assert!((ft - 3.28084).abs() < 0.001);
        }
    
        #[test]
        fn test_color() {
            assert_eq!(describe_color(&Color(255, 0, 0)), "red");
            assert_eq!(describe_color(&Color(128, 128, 128)), "other");
        }
    }

    Deep Comparison

    OCaml vs Rust: pattern tuple struct

    See example.rs and example.ml for implementations.

    Exercises

  • Temperature newtype: Create struct Celsius(f64) and struct Fahrenheit(f64) with conversion functions that destructure in parameters; verify the compiler rejects passing Celsius to a Fahrenheit function.
  • Typed ID: Implement struct UserId(u64) and struct PostId(u64) and write a get_post_for_user(uid: UserId, pid: PostId) -> String function — verify (pid, uid) argument order fails at compile time.
  • Two-field tuple struct: Create struct Range(i32, i32) with methods contains(&self, n: i32) -> bool and overlap(&self, Range(other_start, other_end): &Range) -> bool using tuple struct destructuring.
  • Open Source Repos