ExamplesBy LevelBy TopicLearning Paths
563 Fundamental

Struct Destructuring

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Struct Destructuring" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Extracting multiple fields from a struct simultaneously — without intermediate temporary variables — is a fundamental ergonomic feature of pattern matching languages. Key difference from OCaml: 1. **`..` vs `_`**: Rust uses `..` to ignore remaining fields; OCaml uses `_` in the pattern for each unused field, or relies on the fact that not listing a field is an error without explicit ignore.

Tutorial

The Problem

Extracting multiple fields from a struct simultaneously — without intermediate temporary variables — is a fundamental ergonomic feature of pattern matching languages. Struct destructuring allows binding multiple fields in one pattern, renaming them, ignoring others with .., and doing all of this directly in function parameters. This eliminates boilerplate field access chains (p.x, p.y) in favor of declarative extraction that reads like a specification of what data you need.

🎯 Learning Outcomes

  • • How let Point { x, y } = p; binds struct fields to local names
  • • How Point { name: n, age: a } renames fields during destructuring
  • • How .. ignores remaining fields in a destructuring pattern
  • • How destructuring works directly in function parameters: fn f(Point { x, y }: &Point)
  • • Where struct destructuring reduces boilerplate: geometric computation, configuration extraction
  • Code Example

    #![allow(clippy::all)]
    //! Struct Destructuring
    //!
    //! Extracting fields from structs in patterns.
    
    pub struct Point {
        pub x: i32,
        pub y: i32,
    }
    pub struct Person {
        pub name: String,
        pub age: u32,
    }
    
    /// Basic destructuring.
    pub fn get_x(p: &Point) -> i32 {
        let Point { x, .. } = p;
        *x
    }
    
    /// Full destructuring.
    pub fn distance_from_origin(p: &Point) -> f64 {
        let Point { x, y } = p;
        ((*x as f64).powi(2) + (*y as f64).powi(2)).sqrt()
    }
    
    /// Destructure with rename.
    pub fn describe_person(p: &Person) -> String {
        let Person { name: n, age: a } = p;
        format!("{} is {} years old", n, a)
    }
    
    /// Match with destructuring.
    pub fn quadrant(p: &Point) -> &'static str {
        match p {
            Point { x: 0, y: 0 } => "origin",
            Point { x, y } if *x > 0 && *y > 0 => "Q1",
            Point { x, y } if *x < 0 && *y > 0 => "Q2",
            Point { x, y } if *x < 0 && *y < 0 => "Q3",
            Point { x, y } if *x > 0 && *y < 0 => "Q4",
            _ => "axis",
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_get_x() {
            let p = Point { x: 5, y: 10 };
            assert_eq!(get_x(&p), 5);
        }
    
        #[test]
        fn test_distance() {
            let p = Point { x: 3, y: 4 };
            assert!((distance_from_origin(&p) - 5.0).abs() < 0.001);
        }
    
        #[test]
        fn test_describe() {
            let p = Person {
                name: "Alice".into(),
                age: 30,
            };
            assert!(describe_person(&p).contains("Alice"));
        }
    
        #[test]
        fn test_quadrant() {
            assert_eq!(quadrant(&Point { x: 1, y: 1 }), "Q1");
            assert_eq!(quadrant(&Point { x: 0, y: 0 }), "origin");
        }
    }

    Key Differences

  • **.. vs _**: Rust uses .. to ignore remaining fields; OCaml uses _ in the pattern for each unused field, or relies on the fact that not listing a field is an error without explicit ignore.
  • Parameter destructuring: Both Rust and OCaml support destructuring in function parameters — idiomatic in both languages.
  • Field rename: Rust { field: new_name } renames; OCaml { field = new_name } does the same with = instead of :.
  • Match in let: Rust allows struct destructuring in let bindings, match arms, and parameters; OCaml has the same flexibility.
  • OCaml Approach

    OCaml record destructuring uses the same syntax as pattern matching:

    type point = { x: int; y: int }
    let get_x { x; _ } = x
    let distance_from_origin { x; y } = sqrt (float_of_int (x*x + y*y))
    let f { x; y } { x = x2; y = y2 } = { x = x+x2; y = y+y2 }
    

    Full Source

    #![allow(clippy::all)]
    //! Struct Destructuring
    //!
    //! Extracting fields from structs in patterns.
    
    pub struct Point {
        pub x: i32,
        pub y: i32,
    }
    pub struct Person {
        pub name: String,
        pub age: u32,
    }
    
    /// Basic destructuring.
    pub fn get_x(p: &Point) -> i32 {
        let Point { x, .. } = p;
        *x
    }
    
    /// Full destructuring.
    pub fn distance_from_origin(p: &Point) -> f64 {
        let Point { x, y } = p;
        ((*x as f64).powi(2) + (*y as f64).powi(2)).sqrt()
    }
    
    /// Destructure with rename.
    pub fn describe_person(p: &Person) -> String {
        let Person { name: n, age: a } = p;
        format!("{} is {} years old", n, a)
    }
    
    /// Match with destructuring.
    pub fn quadrant(p: &Point) -> &'static str {
        match p {
            Point { x: 0, y: 0 } => "origin",
            Point { x, y } if *x > 0 && *y > 0 => "Q1",
            Point { x, y } if *x < 0 && *y > 0 => "Q2",
            Point { x, y } if *x < 0 && *y < 0 => "Q3",
            Point { x, y } if *x > 0 && *y < 0 => "Q4",
            _ => "axis",
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_get_x() {
            let p = Point { x: 5, y: 10 };
            assert_eq!(get_x(&p), 5);
        }
    
        #[test]
        fn test_distance() {
            let p = Point { x: 3, y: 4 };
            assert!((distance_from_origin(&p) - 5.0).abs() < 0.001);
        }
    
        #[test]
        fn test_describe() {
            let p = Person {
                name: "Alice".into(),
                age: 30,
            };
            assert!(describe_person(&p).contains("Alice"));
        }
    
        #[test]
        fn test_quadrant() {
            assert_eq!(quadrant(&Point { x: 1, y: 1 }), "Q1");
            assert_eq!(quadrant(&Point { x: 0, y: 0 }), "origin");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_get_x() {
            let p = Point { x: 5, y: 10 };
            assert_eq!(get_x(&p), 5);
        }
    
        #[test]
        fn test_distance() {
            let p = Point { x: 3, y: 4 };
            assert!((distance_from_origin(&p) - 5.0).abs() < 0.001);
        }
    
        #[test]
        fn test_describe() {
            let p = Person {
                name: "Alice".into(),
                age: 30,
            };
            assert!(describe_person(&p).contains("Alice"));
        }
    
        #[test]
        fn test_quadrant() {
            assert_eq!(quadrant(&Point { x: 1, y: 1 }), "Q1");
            assert_eq!(quadrant(&Point { x: 0, y: 0 }), "origin");
        }
    }

    Deep Comparison

    OCaml vs Rust: pattern struct destructuring

    See example.rs and example.ml for implementations.

    Exercises

  • RGB to HSL: Write fn rgb_to_hsl(Color { r, g, b }: Color) -> (f64, f64, f64) using parameter destructuring to extract the color components without field access notation.
  • Config extractor: Create a struct Config { host: String, port: u16, max_connections: usize, timeout_ms: u64 } and write a function that destructures it to build a connection string.
  • Nested destructuring: Write fn summarize(Outer { inner: Inner { value } }: &Outer) -> i32 that extracts the inner value using nested struct destructuring in the parameter.
  • Open Source Repos