RustStructs

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.

  1. Named-field structs — the most common kind; fields have names and types

  2. Tuple structs — fields are positional, accessed by index like a tuple

  3. 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.

RUST
struct User {
    name: String,
    email: String,
    age: u32,
    active: bool,
}

To create an instance you supply values for every field:

RUST
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:

RUST
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"),
);
Tip
Field init shorthand removes redundant repetition. If the variable name matches the field name, write the name just once.
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:

RUST
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);
Warning
Struct update syntax uses move semantics for fields that do not implement `Copy`. After the `..user1` line above, `user1.name` has been moved into `user2` and can no longer be used. `user1.age` (a `u32`) is fine because integers implement `Copy`.
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:

RUST
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.

RUST
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:

RUST
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);
Tip
The newtype pattern catches unit-mismatch bugs at compile time. NASA lost the Mars Climate Orbiter because two teams used different units. Rust's type system can prevent exactly that class of mistake.
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:

RUST
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):

RUST
// 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,
// }
Note
Structs that borrow data need lifetime annotations (the `'a` syntax). This is an advanced topic. When starting out, store owned `String` values to keep things simple.
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:

RUST
#[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 output
Implementing Display
For user-facing output you implement `std::fmt::Display` manually. This controls what appears when you use `` in a format string:

RUST
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 Debug
Field 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:

RUST
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 private
Tip
Keeping fields private and exposing them through methods is the standard encapsulation pattern in Rust. It lets you change internal representation without breaking callers.
Commonly 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

RUST
#[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);
}
Success
You now know all three kinds of Rust structs, how to control field visibility, how to use update syntax and field init shorthand, and how to derive common traits. Next, learn how to add behavior to your structs with methods and associated functions.