Structs

Reviewed & published by Brayan K

Rust is a systems programming language focused on speed, memory safety, and fearless concurrency — and structs are how you model real-world things by grouping related data together.

Part of the free Rust course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll define structs with named fields, create instances, and attach behaviour with methods in impl blocks.

What You'll Learn in This Lesson

1️⃣ Defining and Using Structs

A struct declares a custom type with named, typed fields. You create an instance by setting every field, then read fields with dot notation. To change a field, the whole instance must be declared mut — Rust does not let you mark just one field mutable.

// A struct groups related data under named fields, each with a type.
struct User {
    name: String,
    age: u32,
    active: bool,
}

fn main() {
    // Create an instance by giving every field a value.
    let user = User {
        name: String::from("Ada"),
        age: 36,
        active: true,
    };

    // Access fields with a dot.
    println!("{} is {}", user.name, user.age);   // -> Ada is 36
    println!("active? {}", user.active);          // -> active? true

    // To change a field, the whole instance must be mut.
    let mut user2 = User { name: String::from("Bob"), age: 40, active: false };
    user2.active = true;
    println!("{} active? {}", user2.name, user2.active);   // -> Bob active? true
}

Each instance owns its data. Mutability applies to the whole instance, so user2 had to be mut before we could flip its active field.

2️⃣ Methods and Associated Functions

An impl block attaches behaviour to a struct. A method takes &self and is called with dot syntax on an instance. An associated function has no self and is called on the type with :: — perfect for constructors like new.

// Tuple structs and methods. impl blocks attach behaviour to a struct.
#[derive(Debug)]          // lets us print with {:?}
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // An "associated function" (no self) — often used as a constructor.
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }   // field init shorthand: name == variable
    }

    // A METHOD takes &self to borrow the instance it's called on.
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn is_square(&self) -> bool {
        self.width == self.height
    }
}

fn main() {
    let rect = Rectangle::new(4, 3);     // call the associated function
    println!("{:?}", rect);              // -> Rectangle { width: 4, height: 3 }
    println!("Area: {}", rect.area());   // -> Area: 12
    println!("Square? {}", rect.is_square());   // -> Square? false
}

The Rectangle { width, height } uses field init shorthand: when a variable has the same name as a field, you can write the field once. And #[derive(Debug)] let us print the whole struct with {:?}.

Your turn. Fill in the blanks marked ___, then run it.

// 🎯 YOUR TURN — finish the struct and its method, then run it.
struct Circle {
    radius: f64,
}

impl Circle {
    // 1) Complete this method to return the area: 3.14159 * r * r.
    //    It must borrow self.
    fn area(&self) -> f64 {
        3.14159 * self.___ * self.radius     // 👉 the field name
    }
}

fn main() {
    // 2) Build a Circle with radius 2.0.
    let c = Circle { radius: ___ };          // 👉 the radius value 2.0

    println!("Area: {:.2}", c.area());       // {:.2} rounds to 2 decimals

    // ✅ Expected output:
    //    Area: 12.57
}

Mini-Challenge: A Book Struct

Define a struct with a constructor and two methods. Run it with cargo run and check the output.

// 🎯 MINI-CHALLENGE: a Book struct with methods.
//
// 1. Define a struct Book with fields: title (String), pages (u32),
//    and price (f64).
// 2. Add an impl block with:
//      - new(title, pages, price) -> Book   (a constructor)
//      - summary(&self) -> String that returns
//        format!("{} ({} pages)", self.title, self.pages)
//      - is_long(&self) -> bool that is true when pages > 300
// 3. In main, build a Book "Rust 101", 320 pages, 39.99, then print the
//    summary and whether it is long.
//
// ✅ Example output:
//    Rust 101 (320 pages)
//    Long book? true

fn main() {
    // your code here
}

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Structs

GoalRust Syntax
Definestruct P { x: i32 }
Createlet p = P { x: 1 };
Read fieldp.x
Methodfn area(&self) -> u32
ConstructorP::new(1)
Printable#[derive(Debug)]

🎉 Lesson Complete!

Practice quiz

What does a struct group together?

  • Unrelated functions
  • Only numbers
  • Related data under named fields
  • Threads

Answer: Related data under named fields. A struct groups related data, each value in a named, typed field.

How do you read a field of a struct instance?

  • With dot notation, e.g. user.name
  • With ::
  • With []
  • With get()

Answer: With dot notation, e.g. user.name. Fields are accessed with dot notation like user.name.

To change a single field, what must be true of the instance?

  • The field must be pub
  • It must be cloned
  • It must be boxed
  • The whole instance must be mut

Answer: The whole instance must be mut. Rust requires the whole instance to be mut; you cannot mark one field mutable.

What is the difference between a method and an associated function?

  • Methods are faster
  • A method takes self; an associated function does not
  • Associated functions take self
  • There is none

Answer: A method takes self; an associated function does not. Methods take self and use dot syntax; associated functions take no self and use ::.

How is an associated function like new typically called?

  • Rectangle::new(4, 3)
  • rect.new()
  • new(Rectangle)
  • Rectangle.new

Answer: Rectangle::new(4, 3). Associated functions are called on the type with ::, e.g. Rectangle::new(4, 3).

Why do methods usually take &self instead of self?

  • It is shorter
  • self is invalid
  • &self borrows so the instance stays usable after the call
  • It avoids the heap

Answer: &self borrows so the instance stays usable after the call. &self borrows the instance for reading without consuming it.

What does field init shorthand allow?

  • Skipping fields
  • Writing a field once when a variable shares its name
  • Default values
  • Renaming fields

Answer: Writing a field once when a variable shares its name. When a variable has the same name as a field, you write the field once.

What does #[derive(Debug)] enable?

  • Cloning
  • Equality
  • Mutation
  • Printing with {:?}

Answer: Printing with {:?}. Deriving Debug lets you print the struct with {:?} or {:#?}.

For impl Rectangle with area, what does Rectangle::new(4,3).area() return?

  • 7
  • 12
  • 1
  • false

Answer: 12. area multiplies width by height: 4 * 3 = 12.

What happens if you create a struct but omit a field?

  • It defaults to 0
  • It is set to None
  • A compile error (missing field)
  • Nothing

Answer: A compile error (missing field). Every field must be provided (unless using struct update syntax).

Continue this course

Frequently asked questions

What is the difference between a struct and a tuple?

Both group several values, but a struct gives each value a named field, while a tuple identifies values only by position. With a struct you write user.name and user.age, which is self-documenting; with a tuple you write user.0 and user.1, which is terser but easier to mix up. Use a struct when the values have distinct meanings worth naming, and a tuple for quick, throwaway groupings where the order is obvious.

What is the difference between a method and an associated function?

Both live in an impl block. A method takes self (usually &self) as its first parameter and is called on an instance with dot syntax, like rect.area(). An associated function does not take self and is called on the type with the :: syntax, like Rectangle::new(4, 3). Associated functions are commonly used as constructors. The key difference is that methods operate on a specific instance, while associated functions belong to the type as a whole.

Why do methods usually take &self instead of self?

Taking &self borrows the instance so the method can read its fields without taking ownership, which means you can keep using the instance after calling the method. Taking self by value would move the instance into the method and consume it. Use &self for read-only methods, &mut self when the method needs to modify the instance, and self only in the rarer case where the method should consume the instance.

What does #[derive(Debug)] do?

It automatically generates an implementation of the Debug trait for your struct, which lets you print it with the {:?} placeholder (or the pretty multi-line {:#?}). Without it, trying to print a struct with {:?} fails to compile because Rust does not know how to format your custom type. Deriving Debug is the quick, standard way to make a struct printable for debugging.