Rust: Rust Structures (Struct)
Last updated: 2026-08-26
Structures (Struct) are the core tool for defining custom data types in Rust—they group related data into a meaningful whole.
If a variable is "a single piece of data," then a structure is "a set of related data." It’s like a table—each row is an instance, and each column is a field.
1. What You'll Learn
- Use the
structkeyword to define a named structure - Create a struct instance and access its fields
- Add methods and associated functions using the
implblock - Tuple Struct and Unit Struct
- Ownership and References of Structure Fields
- Syntax for updating structures
..and#[derive(Debug)]
2. Conceptual Diagrams
flowchart LR
subgraph "struct Definition"
DEF["struct User"]
F1["username: String"]
F2["email: String"]
F3["active: bool"]
F4["sign_in_count: u64"]
end
subgraph "Instantiation"
INS["let user = User { ... }"]
V1["username: 'alice'"]
V2["email: 'alice@example.com'"]
V3["active: true"]
V4["sign_in_count: 1"]
end
F1 -.->|"Field Mapping"| V1
F2 -.->|"Field Mapping"| V2
F3 -.->|"Field Mapping"| V3
F4 -.->|"Field Mapping"| V4
3. The Story of a Student Management System
(1) The Struggle: Managing Students with Scattered Variables
Maria is a teacher who needs to manage information about the students in her class. Each student has:
- Name, age, grade, GPA
At first, she wrote:
let name1 = "Xiao Ming";
let age1 = 18;
let grade1 = "Senior Year";
let gpa1 = 3.8;
let name2 = "Xiao Hong";
let age2 = 17;
let grade2 = "11th Grade";
let gpa2 = 3.5;
When the number of students increases to 40, these scattered variables become completely unmanageable. Printing a transcript requires writing 40 lines of code, and changing a single field means making changes in four different places.
(2) The Rust struct approach
struct Student {
name: String,
age: u8,
grade: String,
gpa: f64,
}
fn main() {
let student1 = Student {
name: String::from("Xiao Ming"),
age: 18,
grade: String::from("Senior 3"),
gpa: 3.8,
};
let student2 = Student {
name: String::from("Xiao Hong"),
age: 17,
grade: String::from("Senior 2"),
gpa: 3.5,
};
println!("{}: {} years old, Grade: {}, GPA: {:.1}",
student1.name, student1.age, student1.grade, student1.gpa);
println!("{}: {} years old, Grade: {}, GPA: {:.1}",
student2.name, student2.age, student2.grade, student2.gpa);
}
A struct is like a "template"—it defines what information a "student" should contain. Each instance is like a completed form. Fields are accessed via
., which is clear and secure.
4. Structure Types
graph TB
A[struct Type] --> B[Naming Structures: Fields have names and types]
A --> C[Tuple Structure: The field has a type but no name]
A --> D[Unit Structure: No fields]
B --> E[struct User { name: String, age: u8 }]
C --> F[struct Color(i32, i32, i32)]
D --> G[struct EmptyMarker]
| Type | Definition | Use Cases |
|---|---|---|
| Naming Conventions | struct S { f1: T, f2: T } |
In most cases, field names are self-documenting |
| Tuple Structure | struct S(T1, T2) |
Encapsulates a single concept (e.g., RGB color, coordinates) |
| Unit Structure | struct S; |
Type marker, placeholder for trait implementation |
(2) Methods vs. Association Functions
| Type | First Parameter | Calling Convention | Ownership | Typical Uses |
|---|---|---|---|---|
| Method | &self |
obj.method() |
Borrow | Read instance data |
| Method | &mut self |
obj.method() |
Variable Borrowing | Modify Instance Data |
| Method | self |
obj.method() |
Consumption | Convert/Destroy Instance |
| Related Functions | None | Type::function() |
— | Constructors, Factory Methods |
(3) Quick Reference for Commonly Used Derived Traits
| Trait | Functionality | Automatic Conditions | Example |
|---|---|---|---|
Debug |
{:?} Formatted Print |
Debug All Fields | #[derive(Debug)] |
Clone |
.clone() Deep copy |
Clone all fields | #[derive(Clone)] |
Copy |
Automatic Copy on Assignment | Clone + Copy All Fields | #[derive(Copy, Clone)] |
PartialEq |
== / != Comparison |
All fields implement PartialEq | #[derive(PartialEq)] |
Hash |
Used as a HashMap key | All fields implement Hash | #[derive(Hash)] |
Default |
Default value | All fields have a default | #[derive(Default)] |
5. Structure Examples
▶ Example 1: Complete Usage of Named Structures (Difficulty ⭐)
Output:
Width: <rect.width>, Height: <rect.height>
Width after modification: <mutable_rect.width>
rect: <rect>
rect (pretty): <rect>
// ============================================
// Naming Structures: Definition, Instantiation, Field Access
// ============================================
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
// Create an Instance
let rect = Rectangle {
width: 30,
height: 50,
};
// Access Fields
println!("Width: {}, Height: {}", rect.width, rect.height);
// Variable Instances
let mut mutable_rect = Rectangle {
width: 10,
height: 20,
};
mutable_rect.width = 15; // ✅ Can be modified
println!("Width after modification: {}", mutable_rect.width);
// Use Debug Print
println!("rect: {:?}", rect);
println!("rect (pretty): {:#?}", rect);
}
Output:
Width: 30, Height: 50
Width after modification: 15
rect: Rectangle { width: 30, height: 50 }
rect (pretty): Rectangle {
width: 30,
height: 50,
}
#[derive(Debug)]Allows structs to be printed using the{:?}or{:#?}formatting. This is one of the most commonly used derived traits during debugging.
▶ Example 2: Syntax for Updating Structures and Field Shorthand (Difficulty ⭐⭐)
Output:
user1: <user1.username>
user2: <user2>
user1.active: <user1.active>
// ============================================
// Initializing Field Abbreviations + Update Syntax
// ============================================
#[derive(Debug)]
struct User {
username: String,
email: String,
active: bool,
sign_in_count: u64,
}
fn build_user(username: String, email: String) -> User {
User {
username, // When field names and variable names are the same, they can be written in shorthand.
email, // Equivalent to email: email
active: true,
sign_in_count: 1,
}
}
fn main() {
let user1 = build_user(
String::from("alice"),
String::from("alice@example.com"),
);
// Update Syntax: Based on user1 create user2, modify only email and username
let user2 = User {
email: String::from("alice_new@example.com"),
username: String::from("alice_new"),
..user1 // Other fields copied from user1 (Note: String fields will move!)
};
// println!("user1: {}", user1.username); // ❌ username was moved to user2
println!("user2: {:?}", user2);
println!("user1.active: {}", user1.active); // ✅ bool is Copy, still valid
}
Output:
user2: User { username: "alice_new", email: "alice_new@example.com", active: true, sign_in_count: 1 }
user1.active: true
Field Abbreviation: When a variable name matches a struct field name, the colon in
field: fieldcan be omitted. Updated syntax..copies the remaining fields from another instance—note that this uses move semantics rather than Clone.
▶ Example 3: The impl block—Adding methods to a struct (Difficulty: ⭐⭐)
Output:
rect1 Area: <rect1.area()>
rect1 Can it accommodate? rect2: <rect1.can_hold(&rect2)>
Area of a Square: <square.area()>
// ============================================
// impl block: Methods (&self) and associated functions (no &self)
// ============================================
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// Methods: &self is a reference to a Rectangle instance
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// Associative Functions (similar to static methods): no &self
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 20,
};
let square = Rectangle::square(15); // Correlation functions are used to :: Call
println!("rect1 Area: {}", rect1.area()); // 1500
println!("rect1 Can it accommodate? rect2: {}", rect1.can_hold(&rect2)); // true
println!("Area of a Square: {}", square.area()); // 225
}
Output:
rect1 Area: 1500
rect1 Can it accommodate? rect2: true
Area of a Square: 225
The first parameter of a method (Method) is
&self(or&mut self), and it is called via.. An associated function (Associated Function) does not have&selfand is called via::—a classic example isString::from().
▶ Example 4: Tuple Structures (Difficulty ⭐⭐)
Output:
Black: R=<black.0>, G=<black.1>, B=<black.2>
The Beginning: x=<origin.0>, y=<origin.1>, z=<origin.2>
// ============================================
// Tuple Structure: Suitable for "it's just a bunch of values" scenarios
// ============================================
#[derive(Debug)]
struct Color(i32, i32, i32); // RGB Color
#[derive(Debug)]
struct Point(i32, i32, i32); // 3D Coordinates
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// Fields Accessed via Indexes (tuple-like)
println!("Black: R={}, G={}, B={}", black.0, black.1, black.2);
println!("The Beginning: x={}, y={}, z={}", origin.0, origin.1, origin.2);
// Tuple structures are not the same as tuples!
// let wrong: Color = origin; // ❌ Compilation Error: Point cannot be assigned to Color
// Although their internal structures are the same, they are different types
// This prevents "mixing up colors and coordinates" bugs
}
Output:
<alice.summary()>
<bob.summary()>
<charlie.summary()>
=== Class Rankings ===
#<i + 1>: <s.summary()>
A tuple struct has no field names, only types. It is similar to a tuple but has its own type name.
Color(0,0,0)andPoint(0,0,0)are identical internally but have different types—this prevents confusion.
▶ Example 5: Comprehensive Exercise—Student Management System (Difficulty ⭐⭐⭐)
Output:
<alice.summary()>
<bob.summary()>
<charlie.summary()>
=== Class Rankings ===
#<i + 1>: <s.summary()>
// ============================================
// Comprehensive Example: Structure-Based Approach, Associated Functions and Update Syntax
// ============================================
#[derive(Debug, Clone)]
struct Student {
name: String,
age: u8,
scores: Vec<u32>,
}
impl Student {
fn new(name: &str, age: u8) -> Self {
Student {
name: name.to_string(),
age,
scores: Vec::new(),
}
}
fn add_score(&mut self, score: u32) {
self.scores.push(score);
}
fn average(&self) -> f64 {
if self.scores.is_empty() {
return 0.0;
}
let sum: u32 = self.scores.iter().sum();
sum as f64 / self.scores.len() as f64
}
fn grade(&self) -> char {
match self.average() as u32 {
90..=100 => 'A',
80..=89 => 'B',
70..=79 => 'C',
60..=69 => 'D',
_ => 'F',
}
}
fn summary(&self) -> String {
format!("{} ({} years old) Average: {:.1} Level: {}", self.name, self.age, self.average(), self.grade())
}
}
fn main() {
let mut alice = Student::new("Alice", 20);
alice.add_score(95);
alice.add_score(88);
alice.add_score(92);
println!("{}", alice.summary());
let mut bob = Student::new("Bob", 21);
bob.add_score(72);
bob.add_score(65);
bob.add_score(58);
println!("{}", bob.summary());
let mut charlie = Student {
name: String::from("Charlie"),
..alice.clone()
};
charlie.name = String::from("Charlie");
charlie.add_score(100);
println!("{}", charlie.summary());
let students = [&alice, &bob, &charlie];
println!("\n=== Class Rankings ===");
let mut ranked: Vec<_> = students.iter().collect();
ranked.sort_by(|a, b| b.average().partial_cmp(&a.average()).unwrap());
for (i, s) in ranked.iter().enumerate() {
println!("#{}: {}", i + 1, s.summary());
}
}
Output:
Alice (20 years old) Average: 91.7 Level: A
Bob (21 years old) Average: 65.0 Level: D
Charlie (20 years old) Average: 93.8 Level: A
=== Class Rankings ===
#1: Charlie (20 years old) Average: 93.8 Level: A
#2: Alice (20 years old) Average: 91.7 Level: A
#3: Bob (21 years old) Average: 65.0 Level: D
This example combines the use of the association function
new(), the methodsadd_score()/average()/grade(), and the update syntax..alice.clone()and#[derive(Debug, Clone)]. Structures are the core tools for organizing related data and methods.
❓ FAQ
&self) and associated functions (without &self)?📖 Summary
- Named structures are the most commonly used type of structure, with clear field names
- Tuple structures are suitable for scenarios involving "just a bunch of values" (RGB, coordinates)
- Unit structures have no fields and are used for type annotation.
implAdd methods (&self) and associated functions (none&self) to the structure- Field abbreviation initialization: Omit the colon when the variable name matches the field name
- Syntax update
..other: Copy remaining fields from an instance #[derive(Debug)]Making Structures Printable
📝 Exercises
- Difficulty ⭐: Define a struct
Bookthat containstitle: String,author: String, andyear: u32. Create two instances and print the title and author. - Difficulty ⭐⭐: Add a method
fn perimeter(&self) -> u32toRectangleto calculate the perimeter. Add an associated functionfn from_width(w: u32) -> Rectangleto create a rectangle with equal width and height. - Difficulty ⭐⭐⭐: Define a tuple structure
Distance(f64, f64)to represent distances (in meters and centimeters). Implement a methodfn to_meters(&self) -> f64that returns the total number of meters (for example,Distance(1, 50)returns1.50).