Intermediate · Structs, enums and pattern matching · topic 18 of 50 · 25 min
Structs: your own data types
Explanation
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.
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);
}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.
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.
let mut s = Student { name: String::from("Ravi"), roll: 4, city: String::from("Nagpur"), active: true };
s.city = String::from("Mumbai");Handy shortcuts
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
};Tuple structs and unit structs
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); // 499This 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
#[derive(Debug)]
struct Train {
number: u32,
name: String,
}
fn main() {
let t = Train { number: 12002, name: String::from("Shatabdi") };
println!("{:?}", t);
println!("{:#?}", t);
}#[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
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.
#[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);
}Practice (0/3 done)
Why does this not compile? struct Shop { name: String, open: bool } fn main() { let s = Shop { name: String::from("Kirana") }; }
What does this print? struct Rupees(u64); fn main() { let r = Rupees(250); let total = r.0 * 2; println!("{total}"); }
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
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Methods and associated functionsStructs, enums and pattern matching · same module
- Enums and OptionStructs, enums and pattern matching · same module
- Pattern matching: match, if let and let elseStructs, enums and pattern matching · same module
- Structs and methodsGo Programming (backend and cloud) · Collections and structs
- Pointers to structs and the arrow operatorC Programming (from zero to systems) · Structs, unions, enums and files
- StructsC Programming (from zero to systems) · Structs, unions, enums and files
Questions people ask about this
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.
Can you assign one struct to another of the same type with =?Related
A JSON field may be null or missing. Which field type fits?Related
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
- 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.
- Module 5 exam: Structs, enums and pattern matching · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Error handling and collections. 50 topics left in the course before the final exam.