In the chapter Mutability, we learned about making variables mutable or immutable. Now, let's see how we can group related data together using structs.
The "Contact Card" Analogy
Imagine you want to create a digital contact card for someone. This card would have information like their name, email address, and whether they are active or not. Instead of storing each piece of information in separate variables, it would be much better to bundle them together into a single unit.
Structs help us do exactly that! A struct is a way to create a custom data type that groups related pieces of data together.
Central Use Case: You are building a system to manage users. Each user has a username, email, and an active status. Using a struct, you can group these pieces of information into a single User data type.
Key Concepts
Let's break down the key things to understand about structs:
Definition: A struct is a composite data type that groups multiple values (called fields) into a single unit.
Fields: Each field in a struct has a name and a type (e.g.,
username: String).Instantiation: You create an instance of a struct (also called an object) by providing values for each of its fields.
Accessing Fields: You can access the fields of a struct instance using the dot operator (
.).
Structs in Action
Let's create a User struct:
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
Explanation:
We define a struct named
Userusing thestructkeyword.Inside the curly braces
{}, we define the fields of the struct.Each field has a name (e.g.,
active,username) and a type (e.g.,bool,String,u64).boolis fortrueorfalse,Stringis for text andu64is for counting.
Now, let's create an instance of the User struct:
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
println!("User 1 username: {}", user1.username);
}
Output:
User 1 username: someusername123
Explanation:
We create an instance of the
Userstruct nameduser1.We use the struct name
Userfollowed by curly braces{}to initialize the fields.For each field, we provide a value of the correct type.
We can access the
usernamefield ofuser1usinguser1.username.String::fromis a function to createStringobjects
Let's create a function that takes a user to show how functions can use structs:
fn print_user_email(user: User) {
println!("User email is: {}", user.email);
}
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
print_user_email(user1);
}
Output:
User email is: someone@example.com
Explanation:
print_user_emailtakes aUserobject, it can use all properties inside the user.user.emailis used inside the function to print the email.
Mutability with Structs
Like any other variable, you can make a struct instance mutable using the mut keyword:
fn main() {
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com"); // Now it's allowed!
println!("User 1 email: {}", user1.email);
}
Output:
User 1 email: anotheremail@example.com
Explanation:
We declare
user1as mutable usinglet mut user1 = ....Now we can modify the
emailfield ofuser1usinguser1.email= ....
Ownership and Structs
When you create a struct instance, the struct owns the data stored in its fields. This means that the same ownership rules we learned in Chapter 1: Ownership apply to structs.
For example, if a struct field contains a String, the struct owns that String. When the struct instance goes out of scope, the String data will be dropped (freed from memory).
Internal Implementation (Simplified)
Structs are typically stored in contiguous blocks of memory. This means that the fields of a struct are placed next to each other in memory.
Let's see a simplified sequence diagram of how a struct instance is created and accessed:
Explanation:
When
user1is created, the program asks the computer's memory for a space to hold theUserstruct.The computer gives a memory address. The struct fields (like
active,username, etc.) are stored at that memory address, one after the other.When we access
user1.username, the program goes to the memory address ofuser1and reads the value stored in theusernamefield.When
user1is no longer needed (goes out of scope), the memory is freed up for other things.
Conclusion
Structs are a powerful way to group related data together and create custom data types in Rust. They help organize your code and make it more readable and maintainable. Understanding structs is essential for building more complex programs.
In the next chapter, we'll explore Enums, which allow you to define a type that can have one of several possible values.


