Skip to main content

Command Palette

Search for a command to run...

Structs

The Chapter 5

Published
•4 min read•View as Markdown
T
Dev cum DevRel trying to figure out things.

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:

  1. Definition: A struct is a composite data type that groups multiple values (called fields) into a single unit.

  2. Fields: Each field in a struct has a name and a type (e.g., username: String).

  3. Instantiation: You create an instance of a struct (also called an object) by providing values for each of its fields.

  4. 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 User using the struct keyword.

  • 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). bool is for true or false, String is for text and u64 is 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 User struct named user1.

  • We use the struct name User followed by curly braces {} to initialize the fields.

  • For each field, we provide a value of the correct type.

  • We can access the username field of user1 using user1.username.

  • String::from is a function to create String objects

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_email takes a User object, it can use all properties inside the user.

  • user.email is 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 user1 as mutable using let mut user1 = ....

  • Now we can modify the email field of user1 using user1.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:

  1. When user1 is created, the program asks the computer's memory for a space to hold the User struct.

  2. The computer gives a memory address. The struct fields (like active, username, etc.) are stored at that memory address, one after the other.

  3. When we access user1.username, the program goes to the memory address of user1 and reads the value stored in the username field.

  4. When user1 is 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.

Beginner's Guide to Rust

Part 7 of 14

In this series, we'll dive deep into Rust programming

Up next

Enumerations (Enums)

Chapter 6