Skip to content

Intermediate · Structs, enums and pattern matching · topic 18 of 50 · 25 min

Structs: your own data types

Explanation

A struct groups related values under one name, each with a named field and a type. You create one with StructName { field: value, ... } and read fields with a dot. Rust also has tuple structs (fields without names) and unit structs (no fields). Add #[derive(Debug)] to print a struct with {:?}.

Why structs?

A tuple like ("Asha", 21, "Pune") works, but nobody can tell what 21 means without reading other code. A struct gives each value a name. A struct is a custom data type that groups related values, called fields. It is like a form with labelled boxes.

rust
struct Student {
    name: String,
    roll: u32,
    city: String,
    active: bool,
}

fn main() {
    let s = Student {
        name: String::from("Asha"),
        roll: 21,
        city: String::from("Pune"),
        active: true,
    };
    println!("{} (roll {}) from {}", s.name, s.roll, s.city);
}
The struct definition is a blueprint; the value s is an instance of it. Prints: Asha (roll 21) from Pune

An instance is one actual value made from the blueprint. Field order in the instance does not matter, but every field must be given a value. Rust has no "undefined" or "missing" fields.

Think of it like this: A struct definition is like the blank admission form a college prints: it has labelled boxes for name, roll number and city. Each filled-in form is an instance. Every form has the same boxes, but different students write different values in them, and a form with an empty required box is not accepted.

Structs have no hidden cost. In memory, a struct is just its fields placed next to each other, so using a struct instead of separate variables does not make your program slower.

Changing fields

To change any field, the whole instance must be mut. Rust does not allow marking only some fields as mutable.

rust
let mut s = Student { name: String::from("Ravi"), roll: 4, city: String::from("Nagpur"), active: true };
s.city = String::from("Mumbai");
let mut makes all fields changeable.

Handy shortcuts

rust
fn new_student(name: String, roll: u32) -> Student {
    Student {
        name,             // field init shorthand: same as name: name
        roll,
        city: String::from("Unknown"),
        active: true,
    }
}

let a = new_student(String::from("Neha"), 7);
let b = Student {
    name: String::from("Kiran"),
    roll: 8,
    ..a                   // struct update: take the other fields from a
};
Careful: ..a MOVES a's String fields (city) into b, so a.city can no longer be used.

Tuple structs and unit structs

rust
struct Rupees(u64);          // tuple struct: one unnamed field
struct Point(i32, i32);

struct Online;               // unit struct: no fields at all

let price = Rupees(499);
println!("{}", price.0);     // 499
A tuple struct gives a new name to a simple value, so you cannot mix up Rupees and Paise by accident.

This idea, wrapping a simple value in a named type for safety, is called the newtype pattern. fn pay(amount: Rupees) can never receive a raw number or a distance by mistake.

Printing a struct

rust
#[derive(Debug)]
struct Train {
    number: u32,
    name: String,
}

fn main() {
    let t = Train { number: 12002, name: String::from("Shatabdi") };
    println!("{:?}", t);
    println!("{:#?}", t);
}
{:?} prints Train { number: 12002, name: "Shatabdi" }. {:#?} prints the same over several lines.

#[derive(Debug)] is an attribute that asks the compiler to write the Debug printing code for you. Structs do not get {} printing automatically, because there is no single obvious way to show them to users; you implement Display yourself (module 7).

Ownership inside structs

Fields like String are owned by the struct. When the struct is dropped, all its fields are dropped too. That is why we used String instead of &str: the struct owns its data and needs no lifetimes (see module 4).

Common mistakes

  • Forgetting a field when creating an instance: "missing field city". Give every field a value.
  • Trying to change a field of a non-mut instance. Use let mut.
  • Printing with {} and getting "doesn't implement std::fmt::Display". Use {:?} with #[derive(Debug)].
  • Using ..other and then being surprised that other's String fields were moved.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Structs: your own data types · 1/6

struct = labelled form

A struct is your own data type. It groups related values, each with a name and a type.

Prefer a video by a teacher? Find free videos on “rust structs tutorial derive debug tuple struct” on YouTube ↗

In real life: A product in an online shop

An e-commerce backend models each product as a struct with an id, a name, a price in paise and a stock count. Every function that handles products takes a Product, so nobody can pass the price where the stock was expected. Adding a new field later (say, a GST rate) makes the compiler point at every place that creates a Product, so nothing is forgotten.

rust
#[derive(Debug)]
struct Product {
    id: u32,
    name: String,
    price_paise: u64,
    stock: u32,
}

fn main() {
    let p = Product { id: 101, name: String::from("Steel tiffin"), price_paise: 34_900, stock: 12 };
    println!("{} costs Rs {}.{:02}", p.name, p.price_paise / 100, p.price_paise % 100);
    println!("{:?}", p);
}
Prints: Steel tiffin costs Rs 349.00, then the debug view.

Practice (0/3 done)

Task 1 · Question

Why does this not compile? struct Shop { name: String, open: bool } fn main() { let s = Shop { name: String::from("Kirana") }; }

Task 2 · Question

What does this print? struct Rupees(u64); fn main() { let r = Rupees(250); let total = r.0 * 2; println!("{total}"); }

Task 3 · Practice

Define a struct Book with title (String), author (String), pages (u32) and available (bool). Derive Debug. Create two books, mark one as not available, and print both with {:#?}. Then create a tuple struct Isbn(String) and store one.

Quick quiz

Question 1

1.What is a struct?

Question 2

2.How do you make one field of an instance changeable?

Question 3

3.What does #[derive(Debug)] allow?

Question 4

4.Why use struct Rupees(u64) instead of a plain u64?

Question 5

5.After let b = Student { roll: 2, ..a }; where a has String fields, what is true?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What is a struct?

Answer: A custom type that groups named fields

Structs group related values under one named type.

How do you make one field of an instance changeable?

Answer: Make the whole instance mut

Mutability applies to the whole binding, not individual fields.

What does #[derive(Debug)] allow?

Answer: Printing the struct with {:?}

Debug provides the {:?} format.

Why use struct Rupees(u64) instead of a plain u64?

Answer: The type system stops you mixing rupees with other numbers

The newtype pattern adds type safety at no run-time cost.

After let b = Student { roll: 2, ..a }; where a has String fields, what is true?

Answer: a's String fields were moved into b

Struct update syntax moves non-Copy fields.

How does Go reuse code from another struct type?Related

Answer: Embedding (composition)

Embedding promotes the inner type's fields and methods to the outer type.

From Structs and methods

Can a struct contain a member of its own struct type (not a pointer)?Related

Answer: No: it would be infinitely large; it can contain a pointer to its type

Self-reference must be through a pointer.

From Pointers to structs and the arrow operator

Can you assign one struct to another of the same type with =?Related

Answer: Yes, all members are copied

Struct assignment copies everything.

From Structs

A JSON field may be null or missing. Which field type fits?Related

Answer: Option<String>

Option maps null and missing keys to None.

From Serde and JSON

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Structs, enums and pattern matching” before its exam: Methods and associated functions, Enums and Option, Pattern matching: match, if let and let else.
  2. Module 5 exam: Structs, enums and pattern matching · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Error handling and collections. 50 topics left in the course before the final exam.

← Previous: Lifetimes