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.