Rust offers two principal ways to compose custom data types: structs and enums. A struct groups related fields together, and Rust supports three flavors — named-field structs, tuple structs, and unit structs — so you can pick the form that best fits your data. Behavior is attached separately using an `impl` block, where methods take `&self`, `&mut self`, or `self` as their first parameter to declare how they interact with the receiver.
Enums declare a type whose values are one of several named variants, and each variant can carry its own data. A shape might be either a `Circle(f64)` carrying a radius or a `Rectangle { width: f64, height: f64 }` carrying its dimensions. This is a true sum type: every value encodes both which case it belongs to and the data for that case, which makes enums far more expressive than their counterparts in most other languages.
Pattern matching with `match` is the natural companion to these types. A `match` expression destructures a value and branches on its shape, while the compiler enforces exhaustiveness: every variant must be handled, or the program will not compile. This guarantee eliminates the entire class of bugs where a developer forgets to consider one branch, and it encourages designers to make the state space of a type fully explicit.
For situations where a full `match` would be overkill, Rust offers `if let` to handle a single interesting pattern and a `while let` loop that continues as long as a pattern keeps matching. Both are syntactic sugar for a `match` with one meaningful arm and a wildcard, and they let you write tighter code when the alternatives are not interesting enough to enumerate.