What are Structs?
A struct is short for structure. It’s like a container that groups different pieces of data under one name. Structs are similar to objects in object-oriented programming, but a struct itself doesn’t have methods; it only stores data.
- It can hold different types of values, such as a number, a string, a Boolean.
- It’s like creating your own “data model” (such as a student, book, or car).
Types of Structs in Rust
Rust provides us with different styles of structs, depending on how we want to represent that data.
1) Named Field Structs: This is the most common type of struct. Each field inside the struct has a name and a data type.
For example, if you are building a library system, you may want to store information about a book, such as its title, the number of pages, and its price.
Code example:
struct Book {
title: String,
pages: u32,
price: f32,
}
fn main() {
let book1 = Book {
title: String::from("Rust Guide"),
pages: 250,
price: 19.99,
};
println!("{} has {} pages and costs ${}", book1.title, book1.pages, book1.price);
}
- Here, the name title, pages, and price clearly describe what each value represents.
2) Tuple Structs: Tuple structs look like regular tuples, but they are given a custom type name. The field doesn’t have names; they are accessed by their position index.
Suppose you want to represent colors using three numbers (RGB). Instead of naming each field, you can just group them. For example:
struct Color(u8, u8, u8);
fn main() {
let red = Color(255, 0, 0);
println!("Red color values: {}, {}, {}", red.0, red.1, red.2);
}
- Here, Color(255, 0, 0) represents the RGB values for red. Tuple structs are useful when naming each field separately doesn’t add much value.
3) Unit-like Structs: Sometimes, you may not need to store any data at all; you just want to create a type that can act as a marker or tag. For this reason, unit-like structs are used.
For example:
struct Logger;
fn main() {
let log = Logger; // Just an instance, holds no data
println!("Logger struct created!");
}
How To Use Named Field Structs In Rust?
A Named Field Struct is a way to group related data together, where each piece of data has a clear name. This makes our code easier to read and understand.
Imagine, it’s like a “profile-card” that has a name, an age, and a status like student or not. Each of these is a field inside the struct. You can then create instances (actual “cards”) with specific values and access their data easily.
Example:
// Defining a Named Field Struct
struct Car {
brand: String,
year: u32,
is_electric: bool,
}
fn main() {
// Creating an instance of the struct
let my_car = Car {
brand: String::from("Tesla"),
year: 2023,
is_electric: true,
};
// Accessing fields
println!("Brand: {}", my_car.brand);
println!("Year: {}", my_car.year);
println!("Is Electric: {}", my_car.is_electric);
}
Output:
Brand: Tesla
Year: 2023
Is Electric: true
Explanation of this code:
- struct Car { . . . } → Defines a new data type called Car with three named fields: brand, year, and is_electric.
- let my_car = Car { . . . }; → Creates a specific car instance with actual values.
- my_car.brand → Accesses the brand field of this car.
How To Use Tuple Structs In Rust?
A Tuple Struct is a way to group related data without giving names to each field, but the struct itself has a name.
Example:
// Defining a Tuple Struct
struct Point(i32, i32, i32);
fn main() {
// Creating an instance of the tuple struct
let my_point = Point(10, 20, 30);
// Accessing values using index
println!("X: {}", my_point.0);
println!("Y: {}", my_point.1);
println!("Z: {}", my_point.2);
// Using the values in a calculation
let sum = my_point.0 + my_point.1 + my_point.2;
println!("Sum of coordinates: {}", sum);
}
Output:
X: 10
Y: 20
Z: 30
Sum of coordinates: 60
Explanation:
- struct Point(i32, i32, i32); → Defines a new type Point with three fields (all integers), but no field names.
- let my_point = Point(10, 20, 30); → Creates an instance of Point with values 10, 20, 30.
- my_point.0, my_point.1, my_point.2 → Access fields by their index.
How To Use Unit-like Structs In Rust?
A Unit-like Struct is a struct that has no fields at all. It doesn’t store any data.
They are especially useful when you want to:
- Create a unique type to distinguish values.
- Implement traits on something that doesn’t need data.
Example:
// Defining a Unit-like Struct
struct Logger;
fn main() {
// Creating an instance
let log = Logger;
// Using it to indicate a behavior
print_message(log);
}
// Function that accepts Logger type
fn print_message(_: Logger) {
println!("This is a special logger type!");
}
Output:
This is a special logger type!
Struct Updates Using Struct Update Syntax
Rust allows you to create a new instance of a struct by copying fields from another instance using the . . syntax.
Example:
// Define a Person struct
struct Person {
name: String,
age: u32,
is_student: bool,
}
fn main() {
// Original person
let person1 = Person {
name: String::from("John"),
age: 22,
is_student: true,
};
// Create a new person using struct update syntax
let person2 = Person {
name: String::from("Emma"), // only name is changed
..person1 // rest of the fields copied from person1
};
println!("Person2 Details: Name: {}, Age: {}, Is Student: {}",
person2.name, person2.age, person2.is_student);
}
Output:
Person2 Details: Name: Emma, Age: 22, Is Student: true
Methods in Structs
In Rust, methods let you attach functions directly to a struct. This allows the struct to do actions related to itself, instead of writing separate functions.
Methods help organize code and make it more readable and structured.
impl block → Where you define all methods for a struct.
&self → Means the method can read data from the struct without taking ownership.
Example:
// Define a Rectangle struct
struct Rectangle {
width: u32,
height: u32,
}
// Implement methods for Rectangle
impl Rectangle {
// Method to calculate area
fn area(&self) -> u32 {
self.width * self.height
}
// Method to calculate perimeter
fn perimeter(&self) -> u32 {
2 * (self.width + self.height)
}
}
fn main() {
let my_rect = Rectangle {
width: 15,
height: 25,
};
println!("Rectangle Area: {}", my_rect.area());
println!("Rectangle Perimeter: {}", my_rect.perimeter());
}
Output:
Rectangle Area: 375
Rectangle Perimeter: 80
Exercise: Book Library Struct
create a small library system. Each book has a title, author, and number of pages.
Your task:
- Create a struct Book with fields title, author, and pages.
- Add a method is_long that returns true if the book has more than 300 pages.
- Add another method summary that prints the title and author in a sentence.
- Create at least two books and test the methods.
// Define the Book struct
struct Book {
title: String,
author: String,
pages: u32,
}
// Implement methods for Book
impl Book {
// Method to check if book is long
fn is_long(&self) -> bool {
self.pages > 300
}
// Method to print a short summary
fn summary(&self) {
println!("'{}' is written by {}.", self.title, self.author);
}
}
fn main() {
let book1 = Book {
title: String::from("Rust Programming Basics"),
author: String::from("Alice"),
pages: 250,
};
let book2 = Book {
title: String::from("Mastering Rust"),
author: String::from("Bob"),
pages: 400,
};
// Test methods
book1.summary();
println!("Is it a long book? {}\n", book1.is_long());
book2.summary();
println!("Is it a long book? {}", book2.is_long());
}
Output:
'Rust Programming Basics' is written by Alice.
Is it a long book? false
'Mastering Rust' is written by Bob.
Is it a long book? true