Structs in Rust
Structs are one of Rust's most important tools for organizing related data. A struct lets you define a custom data type by bundling together several values that belong together. Think of a struct as a blueprint — you define the shape once and then create as many instances as you need.
The Three Kinds of Structs
Rust has three flavors of struct. Each has a different syntax and different use cases.
Named-field structs — the most common kind; fields have names and types
Tuple structs — fields are positional, accessed by index like a tuple
Unit structs — no fields at all; mainly used with traits
Named-Field Structs
A named-field struct gives every piece of data a meaningful name. You define it with the struct keyword, a name, and a list of field_name: Type pairs inside curly braces.
struct User {
name: String,
email: String,
age: u32,
active: bool,
}To create an instance you supply values for every field:
let user1 = User {
name: String::from("Alice"),
email: String::from("alice@example.com"),
age: 30,
active: true,
};
println!("Name: {}", user1.name);
println!("Age: {}", user1.age);Field Init Shorthand
When a local variable has the exact same name as a struct field you can skip the repetition — Rust allows a shorthand syntax:
fn build_user(name: String, email: String) -> User {
User {
name, // shorthand for name: name
email, // shorthand for email: email
age: 0,
active: true,
}
}
let user = build_user(
String::from("Bob"),
String::from("bob@example.com"),
);Struct Update Syntax
You often want to create a new instance that is mostly identical to an existing one but with a few fields changed. Struct update syntax (..existing) copies the remaining fields from another instance:
let user1 = User {
name: String::from("Alice"),
email: String::from("alice@example.com"),
age: 30,
active: true,
};
let user2 = User {
email: String::from("alice-new@example.com"),
..user1 // copy all other fields from user1
};
println!("user2 email: {}", user2.email);
println!("user2 age: {}", user2.age);Mutability
Rust does not allow individual fields to be marked mutable — mutability applies to the entire struct instance. Declare the binding with mut and then any field can be reassigned:
let mut user = User {
name: String::from("Charlie"),
email: String::from("charlie@example.com"),
age: 25,
active: true,
};
user.age = 26;
user.active = false;
println!("{} is now age {}", user.name, user.age);Tuple Structs
Tuple structs look like structs but their fields have no names — only types. They are useful when you want a distinct type without inventing field names.
struct Point(f64, f64);
struct Color(u8, u8, u8);
let origin = Point(0.0, 0.0);
let red = Color(255, 0, 0);
println!("x={}, y={}", origin.0, origin.1);
println!("r={}, g={}, b={}", red.0, red.1, red.2);Even though Point and Color have the same underlying shape, they are completely different types. You cannot accidentally pass a Color where a Point is expected.
The Newtype Pattern
Tuple structs with a single field are known as the newtype pattern. They wrap an existing type to create a brand-new type with different semantics:
struct Meters(f64);
struct Kilograms(f64);
fn travel_time(distance: Meters, speed: f64) -> f64 {
distance.0 / speed
}
let d = Meters(100.0);
let k = Kilograms(70.0);
// travel_time(k, 10.0); // compile error — wrong type!
let t = travel_time(d, 10.0);
println!("Time: {:.1} seconds", t);Unit Structs
A unit struct has no fields at all. It takes up no memory and is mainly useful as a marker type when implementing traits:
struct AdminRole;
struct GuestRole;
trait Greet {
fn greet(&self) -> &str;
}
impl Greet for AdminRole {
fn greet(&self) -> &str { "Welcome, administrator." }
}
impl Greet for GuestRole {
fn greet(&self) -> &str { "Welcome, guest." }
}
let role = AdminRole;
println!("{}", role.greet());Ownership and Structs
When you store data in a struct you need to think about ownership. The safest choice is to store owned values (String instead of &str):
// Preferred: owned data — struct owns everything it holds
struct OwnedUser {
name: String,
email: String,
}
// Requires lifetime annotations — more complex
// struct BorrowedUser<'a> {
// name: &'a str,
// email: &'a str,
// }Deriving Debug
By default you cannot print a struct with println!. You need to tell Rust how to display it. The easiest way is to derive the Debug trait:
#[derive(Debug)]
struct Rectangle {
width: f64,
height: f64,
}
let rect = Rectangle { width: 10.0, height: 5.0 };
println!("{:?}", rect); // compact debug output
println!("{:#?}", rect); // pretty-printed debug outputImplementing Display
use std::fmt;
#[derive(Debug)]
struct Rectangle {
width: f64,
height: f64,
}
impl fmt::Display for Rectangle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Rectangle({}w x {}h)", self.width, self.height)
}
}
let rect = Rectangle { width: 10.0, height: 5.0 };
println!("{}", rect); // uses Display
println!("{:?}", rect); // uses DebugField Visibility
By default all struct fields are private — only code in the same module can access them. Use pub to make individual fields publicly accessible:
mod geometry {
pub struct Circle {
pub radius: f64, // publicly readable and writable
center_x: f64, // private — only visible inside this module
center_y: f64,
}
impl Circle {
pub fn new(radius: f64, x: f64, y: f64) -> Circle {
Circle { radius, center_x: x, center_y: y }
}
pub fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
}
let c = geometry::Circle::new(5.0, 0.0, 0.0);
println!("Radius: {}", c.radius);
println!("Area: {:.2}", c.area());
// println!("{}", c.center_x); // compile error — field is privateCommonly Derived Traits
Rust can automatically implement several useful traits for your structs using #[derive(...)]. Here is a quick reference:
Trait | What it gives you | Derive syntax |
|---|---|---|
Debug | Print with {:?} and {:#?} | #[derive(Debug)] |
Clone | Call .clone() to duplicate the struct | #[derive(Clone)] |
Copy | Implicit bitwise copy instead of move | #[derive(Copy, Clone)] |
PartialEq | Compare with == and != | #[derive(PartialEq)] |
Eq | Total equality (requires PartialEq) | #[derive(Eq, PartialEq)] |
Hash | Use struct as a HashMap key | #[derive(Hash, Eq, PartialEq)] |
Default | Create a zero-value default instance | #[derive(Default)] |
Putting It All Together
#[derive(Debug, Clone, PartialEq)]
struct User {
name: String,
email: String,
age: u32,
active: bool,
}
impl User {
fn new(name: &str, email: &str, age: u32) -> User {
User {
name: name.to_string(),
email: email.to_string(),
age,
active: true,
}
}
fn deactivate(&mut self) {
self.active = false;
}
fn is_adult(&self) -> bool {
self.age >= 18
}
}
fn main() {
let mut alice = User::new("Alice", "alice@example.com", 30);
let bob = User {
email: String::from("bob@example.com"),
..alice.clone() // copy all other fields
};
alice.deactivate();
println!("{:#?}", alice);
println!("Bob is adult: {}", bob.is_adult());
println!("alice == bob: {}", alice == bob);
}