Skip to content

Structs and Enums

Structs are the primary mechanism for defining custom types in Rust. Unlike classes in C++ or Java, Structs in Rust do not support inheritance. Composition and trait-based polymorphism are the Idiomatic alternatives.

Unit structs have no fields. They are useful as marker types or phantom types:

struct Marker;
struct Benchmark;
impl Marker {
fn describe(&self) -> &"static str {
"I am a marker type"
}
}

Unit structs have size 0 (they are zero-sized types). This makes them free to create and pass around — the compiler optimizes away all storage for them.

assert_eq!(std::mem::size_of::<Marker>(), 0);

Tuple structs are named tuples. Each field is unnamed but accessible by index:

struct Point(f64, f64);
impl Point {
fn distance_from_origin(&self) -> f64 {
(self.0 * self.0 + self.1 * self.1).sqrt()
}
}
let p = Point(3.0, 4.0);
assert_eq!(p.distance_from_origin(), 5.0);

Tuple structs with a single field implement the newtype pattern, creating a distinct type from The wrapped type:

struct UserId(u64);
struct OrderId(u64);
fn get_user(id: UserId) { /* ... */ }
let uid = UserId(42);
let oid = OrderId(99);
get_user(uid); // OK
// get_user(oid); // ERROR: expected UserId, found OrderId

This is type-safe and zero-cost — the compiler eliminates the wrapper at optimization time.

The most common form of struct definition:

struct Person {
name: String,
age: u32,
email: Option<String>,
}
let alice = Person {
name: String::from("Alice"),
age: 30,
email: Some(String::from("alice@example.com")),
};
println!("{} is {}", alice.name, alice.age);

When the variable name matches the field name, you can use the shorthand:

fn make_person(name: String, age: u32) -> Person {
Person { name, age, email: None }
}

Create a new struct from an existing one, overriding specific fields:

let bob = Person {
name: String::from("Bob"),
..alice // remaining fields copied from alice
};
// bob.name == "Bob", bob.age == 30, bob.email == Some("alice@example.com")

Struct update syntax moves the remaining fields. After ..aliceThe alice binding can no longer Be used in its entirety (it is partially moved), but individual Copy fields remain accessible.

By default, the compiler is free to reorder fields and add padding for alignment. The #[repr] Attribute controls the memory layout:

#[repr(C)] // C-compatible layout — fields in declaration order, C alignment rules
struct Color {
r: u8,
g: u8,
b: u8,
}
#[repr(transparent)] // has the same layout as the single field inside
struct Wrapper(u32);
#[repr(packed)] // no padding — fields are packed tightly (may cause unaligned access)
struct Packed {
a: u8,
b: u32, // at offset 1, not offset 4 — misaligned on most platforms
}
#[repr(align(16))] // forced alignment of 16 bytes
struct Aligned {
data: [u8; 32],
}
## Intuition

Structs group related data under named fields, while enums represent variants where exactly one variant is active at a time. Rust enums are algebraic data types: each variant can carry different data, enabling pattern matching that the compiler verifies for exhaustiveness. Structs are value types that move on assignment unless they implement Copy. Methods are defined in impl blocks, and associated functions (like constructors) are called with :: syntax.

  • [[rust/02-ownership-borrowing/ownership]] - Value types and move semantics
  • [[rust/04-error-handling/error-handling]] - Result and Option enums
  • [[rust/05-traits-generics/traits-and-generics]] - Trait implementations for custom types
  • [[rust/03-structs-enums/advanced-patterns]] - Pattern matching and destructuring