Struct Destructuring
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
let Point { x, y } = p; binds struct fields to local namesPoint { name: n, age: a } renames fields during destructuring.. ignores remaining fields in a destructuring patternfn f(Point { x, y }: &Point)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.{ field: new_name } renames; OCaml { field = new_name } does the same with = instead of :.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");
}
}#[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
fn rgb_to_hsl(Color { r, g, b }: Color) -> (f64, f64, f64) using parameter destructuring to extract the color components without field access notation.struct Config { host: String, port: u16, max_connections: usize, timeout_ms: u64 } and write a function that destructures it to build a connection string.fn summarize(Outer { inner: Inner { value } }: &Outer) -> i32 that extracts the inner value using nested struct destructuring in the parameter.