Heap-allocated types, those are moved (not copied) into the new struct. After the spread, the Original struct is no longer usable in its entirety.
struct Point { x : f64 , y : f64 }
let p = Point { x : 1.0 , y : 2.0 };
let Point { x : a, y : b } = p;
fn swap (( mut x, mut y) : ( i32 , i32 )) -> ( i32 , i32 ) {
std :: mem :: swap ( &mut x, &mut y);
assert_eq! ( swap (( 1 , 2 )), ( 2 , 1 ));
fn unwrap_or < T , E >(result : Result < T , E >, default : T ) -> T {
Result :: Ok (value) => value,
Result :: Err (_) => default,
Tuple structs with a single field are the standard form of the newtype pattern:
struct PhoneNumber ( String );
fn send_email (to : Email , body : & str ) {
println! ( "sending to {}: {}" , to . 0 , body);
let email = Email ( String :: from ( "user@example.com" ));
send_email (email, "hello" );
// send_email(PhoneNumber(String::from("555-1234")), "hello"); // ERROR
#[derive( Debug , Clone , PartialEq , Eq , Hash )]
#[derive( Debug , Clone , PartialEq , Eq , Hash )]
assert_ne! (uid, oid); // different types, even though inner values are equal
Marker types carry no data but encode information in the type system:
struct Distance < Unit >( f64 , std :: marker :: PhantomData < Unit >);
fn add_km (a : Distance < Kilometers >, b : Distance < Kilometers >) -> Distance < Kilometers > {
Distance (a . 0 + b . 0 , PhantomData )
fn add_mi (a : Distance < Miles >, b : Distance < Miles >) -> Distance < Miles > {
Distance (a . 0 + b . 0 , PhantomData )
Enums can carry different data payloads per variant, making them algebraic data types:
Add ( Box < Expr >, Box < Expr >),
Mul ( Box < Expr >, Box < Expr >),
fn eval (expr : & Expr , env : & std :: collections :: HashMap < String , i64 >) -> i64 {
Expr :: Add (l, r) => eval (l, env) + eval (r, env),
Expr :: Mul (l, r) => eval (l, env) * eval (r, env),
Expr :: Var (name) => env . get (name) . copied () . unwrap_or ( 0 ),
Without BoxRecursive enums would be infinitely sized:
// This does NOT compile, infinite size
// enum Expr { Add(Expr, Expr) }
// Fix: Box the recursive variants
Add ( Box < Expr >, Box < Expr >), // pointer-sized (8 bytes)
The #[non_exhaustive] attribute prevents downstream crates from constructing the enum or matching Exhaustively on it. This allows you to add variants in a semver-compatible way:
// In a downstream crate:
ApiVersion :: V1 => println! ( "v1" ),
ApiVersion :: V2 => println! ( "v2" ),
_ => println! ( "unknown version" ), // required because of #[non_exhaustive]
// ApiVersion::V1 // ERROR: cannot construct variants of #[non_exhaustive] enum
Enums can contain other enums, enabling expressive type hierarchies:
Object ( Vec <( String , Value )>),
fn parse_value (input : & str ) -> Value {
} else if input == "true" {
} else if let Ok (n) = input . parse :: < f64 >() {
Value :: String (input . to_string ())
Connecting { addr : String , attempts : u32 },
Connected { addr : String , stream : std :: net :: TcpStream },
Failed { addr : String , error : String },
fn connect (addr : & str ) -> Self {
ConnectionState :: Connecting {
if let ConnectionState :: Connecting { attempts, .. } = self {
fn is_connected ( & self ) -> bool {
matches! ( self , ConnectionState :: Connected { .. })
Enums do not implement Iterator by default. Use a helper to iterate over variants:
#[derive( Debug , Clone , Copy )]
fn all () -> & "static [Color] {
&[Color::Red, Color::Green, Color::Blue]
for color in Color::all() {
For exhaustive iteration, use the strum crate’s EnumIter derive macro:
use strum :: IntoEnumIterator ;
use strum_macros :: EnumIter ;
#[derive( EnumIter , Debug )]
for dir in Direction :: iter () {
You can implement methods only for specific trait bounds:
impl < T : std :: fmt :: Display > Container < T > {
println! ( "{}" , self . data);
impl < T : std :: fmt :: Debug > Container < T > {
println! ( "{:?}" , self . data);
let c1 = Container { data : 42 };
let c2 = Container { data : "hello" };
Rust has no inheritance. Composition is the primary mechanism for code reuse:
fn new (make : & str , model : & str , hp : u32 , fuel : & str , wheel_count : u8 , wheel_diameter : u16 ) -> Self {
fuel_type : fuel . to_string (),
diameter_inches : wheel_diameter,
model : model . to_string (),
println! ( "{} {} with {}hp {} engine and {}x{} \" wheels" ,
self . engine . horsepower, self . engine . fuel_type,
self . wheels . count, self . wheels . diameter_inches);
fn compute ( & self ) -> i32 {
fn deref ( & self ) -> & Self :: Target {
inner : Inner { value : 21 },
extra : String :: from ( "metadata" ),
assert_eq! (outer . compute (), 42 ); // delegated via Deref
Add ( Box < Expr >, Box < Expr >),
Mul ( Box < Expr >, Box < Expr >),
fn eval (expr : & Expr ) -> i64 {
Expr :: Add (l, r) => eval (l) + eval (r),
Expr :: Mul (l, r) => eval (l) * eval (r),
Box :: new ( Expr :: Literal ( 3 )),
Box :: new ( Expr :: Literal ( 4 )),
Box :: new ( Expr :: Literal ( 5 )),
assert_eq! ( eval ( & expr), 23 );
let leaf = Rc :: new ( Node { value : 1 , children : vec! [] });
let branch = Rc :: new ( Node {
children : vec! [ Rc :: clone ( & leaf)],
assert_eq! ( Rc :: strong_count ( & leaf), 2 );
use serde :: { Serialize , Deserialize };
#[derive( Serialize , Deserialize )]
#[serde(rename = "type" )]
#[serde(alias = "hostname" )]
#[serde(default = "default_port" )]
fn default_port () -> u16 { 8080 }
use serde :: { Serialize , Deserialize };
#[derive( Serialize , Deserialize )]
Request { id : u64 , method : String },
Response { id : u64 , status : u16 },
Error { id : u64 , message : String },
Newtype field access verbosity. Accessing the inner value requires .0Which is not descriptive. Implement methods or Deref to provide a cleaner API, but be aware that Deref weakens type safety.
Builder pattern forgetting required fields. A basic builder that validates in build() only catches missing fields at runtime. Use the typestate pattern to enforce required fields at compile time.
Enum size explosion. An enum’s size is the size of its largest variant plus the discriminant. If one variant is much larger than the others, box it: Large(Box<String>) instead of Large(String).
Typestate pattern and partial moves. The typestate pattern consumes self in each transition. If you need to inspect the state before transitioning, clone or borrow the relevant fields before calling the transition method.
#[non_exhaustive] on structs. #[non_exhaustive] on a struct prevents downstream crates from constructing it and from fully destructuring it (the .. pattern is required). Provide a constructor function to allow construction.
PhantomData affecting drop order. PhantomData<T> makes the compiler treat the struct as if it owns a T. If T has a destructor, the compiler will require the struct to live no longer than T. Use PhantomData<*const T> or PhantomData<fn() -> T> to change variance without affecting the drop check.
Struct update syntax with mutable borrows. Struct { ..other } moves fields. If other is borrowed, you cannot spread from it. Clone the struct first or use individual field copies.
Overusing enum dispatch. Enum dispatch requires all variants to be known at compile time. If you need extensibility (plugins, user-defined types), use trait objects or generics.
ZST edge cases. While ZSTs have zero size, they are not “no type.” A function taking () as a parameter still has a calling convention. A Vec<()> still has length and capacity metadata. ZSTs are real types with real semantics.
repr(transparent) with multiple fields. repr(transparent) requires exactly one non-zero- sized field. If your wrapper has multiple fields, the compiler will reject it. Use a single field wrapper or a different repr attribute.
graph TD
A[Need to model domain concepts?] --> B{Type safety critical?}
B -->|Yes| C{Same representation as another type?}
C -->|Yes| D[Newtype pattern]
C -->|No| E{Complex construction?}
E -->|Yes| F[Builder pattern]
E -->|No| G[Plain struct]
B -->|No| G
A --> H{Need state transitions?}
H -->|Yes, compile-time enforced| I[Typestate pattern]
H -->|Yes, runtime checked| J[State field with enum]
A --> K{Closed set of behaviors?}
K -->|Yes| L[Enum dispatch]
K -->|No, open set| M[Traits + generics or trait objects] This topic covers the core concepts of advanced struct and enum patterns, including underlying theory, practical implementation, and key applications.
Key concepts include:
core concepts and terminology algorithms and computational thinking practical implementation security and ethical considerations applications in the real world Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Problem. Create a Celsius newtype that prevents mixing with Fahrenheit and implements arithmetic operators.
Solution.
#[derive( Debug , Clone , Copy )]
fn from_fahrenheit (f : f64 ) -> Self {
Celsius ((f - 32.0 ) * 5.0 / 9.0 )
fn to_fahrenheit ( self ) -> f64 {
self . 0 * 9.0 / 5.0 + 32.0
impl std :: ops :: Add for Celsius {
fn add ( self , rhs : Self ) -> Self :: Output {
impl std :: ops :: Mul < f64 > for Celsius {
fn mul ( self , rhs : f64 ) -> Self :: Output {
let boiling = Celsius ( 100.0 );
let freezing = Celsius :: from_fahrenheit ( 32.0 );
let doubled = boiling * 2.0 ;
assert_eq! (doubled . 0 , 200.0 );
The newtype has zero runtime cost (the wrapper is optimised away) but the compiler rejects Celsius + Fahrenheit at the type level.
■ \blacksquare ■
Problem. Implement a Shape enum that computes area using exhaustive pattern matching.
Solution.
Rectangle { width : f64 , height : f64 },
Triangle { base : f64 , height : f64 },
Shape :: Circle { radius } => std :: f64 :: consts :: PI * radius * radius,
Shape :: Rectangle { width, height } => width * height,
Shape :: Triangle { base, height } => 0.5 * base * height,
Shape :: Circle { radius : 3.0 },
Shape :: Rectangle { width : 4.0 , height : 5.0 },
Shape :: Triangle { base : 6.0 , height : 2.0 },
println! ( "Area: {:.2}" , shape . area ());
The match is exhaustive: adding a new variant to Shape forces all match expressions to be updated, preventing missing-case bugs.
■ \blacksquare ■
Newtype pattern wraps a type for type safety with zero runtime cost; implement Deref for ergonomic access. Enums with associated data model algebraic data types; exhaustive match prevents missing cases. Builder pattern provides a fluent API for complex struct construction with optional fields. #[non_exhaustive] prevents downstream crates from exhaustively matching on enums or struct fields.Type-state pattern moves validation to compile time by encoding state in generic type parameters.