Defining and using Structs

You can define a struct using the struct keyword followed by the name of the struct and a block of curly braces. Inside the block, you define the fields of the struct. Each field has a name and a type, different fields in a struct can have different types.

Let's get started, run the following command to create a new Rust project:

cargo new structs
cd structs

Let's define a struct named User with three fields: id, username, and email. The id field is of type i32, and the username and email fields are of type String. Here's the code:

struct User {
  id: i32,
  username: String,
  email: String,
}

Learn Rust by Practice

Master Rust through hands-on coding exercises and real-world examples.

Creating instances of a struct

After you define a struct, you can create instances of the struct by using the struct_name { field: value, field: value } syntax. Let's create an instance of our User struct:

fn main() {
  let user = User {
    id: 1,
    username: String::from("john_doe"),
    email: String::from("[email protected]"),
  };
}

In this example, we create a new instance of the User struct with the id field set to 1, the username field set to john_doe, and the email field set to [email protected].

Accessing fields of a struct

You can then access the fields of a struct using the dot notation. Here's an example:

fn main() {
  let user = User {
    id: 1,
    username: String::from("john_doe"),
    email: String::from("[email protected]"),
  };
 
  println!("User ID: {}", user.id);
  println!("Username: {}", user.username);
  println!("Email: {}", user.email);
}

In this example, we access the id, username, and email fields of the user instance using the dot notation.

If you have variables with the same name as the fields of a struct, you can use the struct init shorthand syntax to create an instance of the struct. Here's an example:

fn main() {
  let username = String::from("john_doe");
  let email = String::from("[email protected]");
 
  let user = User { id: 1, username, email };
}

Notice that we didn't have to specify username: username and email: email in this example. Rust automatically assigns the values of the username and email variables to the username and email fields of the User struct because the variable names match the field names, this shorthand is called the struct init shorthand.

If you have another instance and want to copy some of its fields to a new instance, you can use the struct update syntax. Here's an example:

fn main() {
  let user1 = User {
    id: 1,
    username: String::from("john_doe"),
    email: String::from("[email protected]"),
  };
 
  let user2 = User {
    id: 2,
    ..user1
  };
}

In this example, we create a new instance of the User struct named user1. Then, we create another instance named user2 using the struct update syntax. We specify the id field of user2 and use the .. syntax followed by the user1 instance to copy the username and email fields from user1 to user2. This is called the struct update syntax.

Debugging structs

If you want to print the contents of a struct for debugging purposes, you can use the {:?} format specifier with the println! macro. Here's an example:

struct Dog {
  name: String,
  breed: String,
}
 
fn main() {
  let dog = Dog {
    name: String::from("Buddy"),
    breed: String::from("Golden Retriever"),
  };
 
  println!("{:?}", dog);
}

However, if you try to compile the code, you'll get an error:

Debugging struct error Debugging struct error

The reason for this error is that, in order to print a value to the console, it must implement the Debug trait. You don't need to understand traits yet, and you don't need to know how to implement them, because Rust provides us a macro that will automatically implement the Debug trait on structs for us.

To fix this error, you can derive macro along with the Debug trait for the Dog struct by adding #[derive(Debug)] above the struct definition.

A macro is just code that writes code. In this case, the derive macro writes the code to implement the Debug trait for the Dog struct, since implementing the Debug trait is such a common thing in Rust, we don't need to repetitively implement them manually every time we define a new struct, therefore we use the derive macro to automatically implement them for us. Both the Debug trait and the derive macro are part of the Rust standard library and are part of the prelude, so you don't need to import them using the use keyword.

Here's the updated code:

#[derive(Debug)]
struct Dog {
  name: String,
  breed: String,
}
...

Now, if you run the code, you'll see the contents of the Dog struct printed to the console.

Debugging a struct Debugging a struct

The #[derive(Debug)] attribute is a Rust macro that automatically implements the Debug trait for the Dog struct. This allows you to print the contents of the struct using the {:?} format specifier.


That's not all, structs can also have methods and associated functions, we'll learn about them in the next lesson.