Core Concept: Structs & Enums ​
Updated Mar 2026Overview ​
Structs and enums are Rust's primary data modeling tools. Structs group related fields. Enums represent a value that can be one of several variants. Combined with impl blocks, they form Rust's approach to data + behavior.
Struct Types ​
Named Field Structs ​
rust
#[derive(Debug)]
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
fn main() {
// Create instance
let user1 = User {
email: String::from("alice@example.com"),
username: String::from("alice"),
active: true,
sign_in_count: 1,
};
// Field init shorthand (variable name matches field name)
let email = String::from("bob@example.com");
let username = String::from("bob");
let user2 = User {
email, // Same as email: email
username, // Same as username: username
active: true,
sign_in_count: 1,
};
// Struct update syntax (spread remaining fields)
let user3 = User {
email: String::from("charlie@example.com"),
..user2 // Take remaining fields from user2 (user2 partially moved!)
};
}Tuple Structs ​
rust
struct Color(u8, u8, u8);
struct Point(f64, f64, f64);
fn main() {
let red = Color(255, 0, 0);
let origin = Point(0.0, 0.0, 0.0);
// Access by index
println!("Red: {}", red.0);
println!("X: {}", origin.0);
// Destructure
let Color(r, g, b) = red;
println!("RGB: {r}, {g}, {b}");
}Unit Structs ​
rust
struct AlwaysEqual;
// Useful for trait implementations without data
impl PartialEq for AlwaysEqual {
fn eq(&self, _other: &Self) -> bool {
true
}
}impl Blocks ​
rust
#[derive(Debug)]
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
// Associated function (constructor — no self)
fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
fn square(size: f64) -> Self {
Rectangle { width: size, height: size }
}
// Method (borrows self)
fn area(&self) -> f64 {
self.width * self.height
}
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
// Method that mutates
fn scale(&mut self, factor: f64) {
self.width *= factor;
self.height *= factor;
}
// Method that takes ownership
fn into_square(self) -> Rectangle {
let side = (self.width + self.height) / 2.0;
Rectangle::square(side)
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let mut rect = Rectangle::new(30.0, 50.0);
println!("Area: {}", rect.area());
rect.scale(2.0);
println!("Scaled area: {}", rect.area());
let square = Rectangle::square(10.0);
println!("Can hold square: {}", rect.can_hold(&square));
}Enums ​
rust
#[derive(Debug)]
enum Message {
Quit, // No associated data
Move { x: i32, y: i32 }, // Named fields (like a struct)
Write(String), // Single unnamed field
ChangeColor(u8, u8, u8), // Multiple unnamed fields
}
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("Quitting"),
Message::Move { x, y } => println!("Moving to ({x}, {y})"),
Message::Write(text) => println!("Writing: {text}"),
Message::ChangeColor(r, g, b) => println!("Color: ({r}, {g}, {b})"),
}
}
}
fn main() {
let messages = vec![
Message::Quit,
Message::Move { x: 10, y: 20 },
Message::Write("hello".to_string()),
Message::ChangeColor(255, 0, 128),
];
for msg in &messages {
msg.call();
}
}Option<T> ​
rust
fn find_user(id: u32) -> Option<String> {
match id {
1 => Some("Alice".to_string()),
2 => Some("Bob".to_string()),
_ => None,
}
}
fn main() {
// Pattern matching
match find_user(1) {
Some(name) => println!("Found: {name}"),
None => println!("Not found"),
}
// if let
if let Some(name) = find_user(2) {
println!("Found: {name}");
}
// Combinators
let name = find_user(1)
.map(|n| n.to_uppercase()) // Transform if Some
.unwrap_or("unknown".to_string()); // Default if None
let name = find_user(99)
.unwrap_or_default(); // String::default() = ""
// Chaining Options
let result = find_user(1)
.and_then(|name| {
if name.len() > 3 { Some(name) } else { None }
});
// unwrap (panics if None — use only in tests/examples)
let name = find_user(1).unwrap();
let name = find_user(1).expect("User 1 must exist");
}Result<T, E> ​
rust
use std::num::ParseIntError;
fn parse_number(s: &str) -> Result<i32, ParseIntError> {
s.parse::<i32>()
}
fn double_parse(s: &str) -> Result<i32, String> {
let n = s.parse::<i32>()
.map_err(|e| format!("Parse error: {e}"))?;
Ok(n * 2)
}
fn main() {
// Pattern matching
match parse_number("42") {
Ok(n) => println!("Parsed: {n}"),
Err(e) => println!("Error: {e}"),
}
// Combinators
let result = parse_number("42")
.map(|n| n * 2) // Transform if Ok
.unwrap_or(0); // Default if Err
// Converting Result to Option
let opt: Option<i32> = parse_number("42").ok(); // Some(42)
let opt: Option<i32> = parse_number("abc").ok(); // None
// Converting Option to Result
let result: Result<i32, &str> = Some(42).ok_or("missing value");
}Enum with Methods (State Machine) ​
rust
enum TrafficLight {
Red,
Yellow,
Green,
}
impl TrafficLight {
fn duration_seconds(&self) -> u32 {
match self {
TrafficLight::Red => 60,
TrafficLight::Yellow => 5,
TrafficLight::Green => 45,
}
}
fn next(&self) -> TrafficLight {
match self {
TrafficLight::Red => TrafficLight::Green,
TrafficLight::Yellow => TrafficLight::Red,
TrafficLight::Green => TrafficLight::Yellow,
}
}
}Combining Structs and Enums ​
rust
#[derive(Debug)]
struct Order {
id: u64,
items: Vec<String>,
status: OrderStatus,
}
#[derive(Debug)]
enum OrderStatus {
Pending,
Processing { estimated_minutes: u32 },
Shipped { tracking: String },
Delivered,
Cancelled { reason: String },
}
impl Order {
fn is_active(&self) -> bool {
!matches!(self.status, OrderStatus::Delivered | OrderStatus::Cancelled { .. })
}
}