Structs, Enums & Traits
Rust structs, enums with data, trait definitions and implementations, and common patterns
Rust has no classes. Instead, you define data with structs and enums, and behavior with traits and impl blocks. Composition replaces inheritance.
Structs
// Named fields (most common)
struct User {
name: String,
age: u32,
active: bool,
}
let user = User {
name: String::from("Ada"),
age: 30,
active: true,
};
// Access fields
println!("{}", user.name);
// Mutable struct to modify fields
let mut user = User { name: String::from("Ada"), age: 30, active: true };
user.age = 31;
// Struct update syntax (like JS spread)
let user2 = User { name: String::from("Grace"), ..user };
// user2 inherits age and active from user
// WARNING: user.name is now MOVED — cannot use user.name anymore
// Tuple struct (named tuple)
struct Point(i32, i32);
let p = Point(3, 4);
println!("x={}, y={}", p.0, p.1);
// Unit struct (no fields — marker type)
struct Initialized;Methods with impl
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// Associated function (like a static method — no &self)
fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
fn square(size: u32) -> Self {
Self { width: size, height: size }
}
// Method — takes &self (immutable borrow)
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// Mutable method — takes &mut self
fn double(&mut self) {
self.width *= 2;
self.height *= 2;
}
// Consuming method — takes self (ownership)
fn into_parts(self) -> (u32, u32) {
(self.width, self.height)
}
}
// Usage
let rect = Rectangle::new(10, 20);
println!("area: {}", rect.area());
let mut square = Rectangle::square(5);
square.double();Derive macros
Automatically implement common traits:
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 1, y: 2 };
let p2 = p1.clone();
println!("{:?}", p1); // Debug: Point { x: 1, y: 2 }
assert_eq!(p1, p2); // PartialEq
Common derive targets: Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default.
Enums
Enums in Rust can carry data — they’re sum types (algebraic data types), not just named constants.
// Simple enum (like TypeScript string enum)
enum Direction {
Up,
Down,
Left,
Right,
}
// Enum with data
enum Shape {
Circle(f64), // tuple variant
Rectangle { width: f64, height: f64 }, // struct variant
Triangle(f64, f64, f64), // tuple variant with 3 fields
}
// Enum with different data per variant
enum Message {
Quit, // no data
Move { x: i32, y: i32 }, // struct variant
Write(String), // tuple variant
ChangeColor(u8, u8, u8), // tuple variant
}
// Pattern matching
fn process(msg: Message) {
match msg {
Message::Quit => println!("quit"),
Message::Move { x, y } => println!("move to ({}, {})", x, y),
Message::Write(text) => println!("write: {}", text),
Message::ChangeColor(r, g, b) => println!("color: ({}, {}, {})", r, g, b),
}
}
// if let for single variant
if let Message::Write(text) = msg {
println!("{}", text);
}Option and Result are enums
// Option is just an enum
enum Option<T> {
Some(T),
None,
}
// Result is just an enum
enum Result<T, E> {
Ok(T),
Err(E),
}Enums with methods
enum Coin {
Penny,
Nickel,
Dime,
Quarter(String), // state on quarter
}
impl Coin {
fn value(&self) -> u32 {
match self {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {}", state);
25
}
}
}
}Traits
Traits define shared behavior — similar to TypeScript interfaces but with implementations.
// Define a trait
trait Summary {
fn summarize(&self) -> String;
// Default implementation
fn preview(&self) -> String {
format!("{}...", self.summarize().chars().take(20).collect::<String>())
}
}
// Implement for a type
struct Article {
title: String,
content: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("{}: {}", self.title, self.content)
}
// preview gets the default implementation
}
let article = Article {
title: "Rust".to_string(),
content: "A systems programming language".to_string(),
};
println!("{}", article.summarize());
println!("{}", article.preview());Trait as parameter
// impl Trait syntax (simpler)
fn print_summary(item: &impl Summary) {
println!("{}", item.summarize());
}
// Trait bound syntax (more explicit, needed for multiple bounds)
fn print_summary<T: Summary>(item: &T) {
println!("{}", item.summarize());
}
// Multiple trait bounds
fn display<T: Summary + std::fmt::Display>(item: &T) {
println!("{} — {}", item, item.summarize());
}
// where clause (cleaner for complex bounds)
fn process<T, U>(a: &T, b: &U) -> String
where
T: Summary + Clone,
U: Summary,
{
format!("{} + {}", a.summarize(), b.summarize())
}Common standard library traits
// Debug — for {:?} printing
#[derive(Debug)]
struct Point { x: i32, y: i32 }
println!("{:?}", point);
// Clone — for deep copying
#[derive(Clone)]
struct Data { values: Vec<i32> }
let d2 = d1.clone();
// Copy — for implicit copy (stack types only)
#[derive(Copy, Clone)]
struct Coord { x: i32, y: i32 }
let c2 = c1; // copied, not moved
// Default
#[derive(Default)]
struct Config {
host: String, // defaults to ""
port: u16, // defaults to 0
verbose: bool, // defaults to false
}
let config = Config::default();
let custom = Config { port: 8080, ..Config::default() };
// Display — for {} printing
impl std::fmt::Display for Point {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
// From / Into — type conversion
impl From<i32> for Score {
fn from(value: i32) -> Self {
Score { points: value }
}
}
let score: Score = Score::from(100);
let score: Score = 100.into(); // also works
Associated types in traits
trait Container {
type Item; // associated type
fn get(&self, index: usize) -> Option<&Self::Item>;
fn len(&self) -> usize;
}
struct Stack<T> {
items: Vec<T>,
}
impl<T> Container for Stack<T> {
type Item = T;
fn get(&self, index: usize) -> Option<&Self::Item> {
self.items.get(index)
}
fn len(&self) -> usize {
self.items.len()
}
}Trait objects (dynamic dispatch)
When you need runtime polymorphism (like a TypeScript interface):
// dyn Trait — heap-allocated, dynamic dispatch
fn print_all(items: Vec<Box<dyn Summary>>) {
for item in items {
println!("{}", item.summarize());
}
}
// Or with references
fn print_summaries(items: &[&dyn Summary]) {
for item in items {
println!("{}", item.summarize());
}
}
let article = Article { /* ... */ };
let tweet = Tweet { /* ... */ };
print_all(vec![Box::new(article), Box::new(tweet)]);Note: Trait objects (
dyn Trait) use dynamic dispatch (vtable lookup at runtime). Generic bounds (impl Trait/<T: Trait>) use static dispatch (monomorphization at compile time). Prefer generics for performance; use trait objects when you need heterogeneous collections.