Structs

In Rust, there are three forms of structs that we can declare. The simplest of them is the unit struct, which is written with the struct keyword, followed by its name and a semicolon at the end. The following code example defines a unit struct:

// unit_struct.rs

struct Dummy;

fn main() {
let value = Dummy;
}

We have defined a unit struct called Dummy in the preceding code. In main, we can initialize this type using only its name. value now contains an instance of Dummy and is a zero sized value. Unit structs do not take any size at runtime as they have no data associated with them. There are very few use cases for unit structs. They can be used to model entities with no data or state associated with them. Another use case is to use them to represent error types, where the struct itself is sufficient to understand the error without needing a description of it. Another use case is to represent states in a state machine implementation. Next, let's look at the second form of structs.

The second form of struct is the tuple struct, which has associated data. Here, the individual fields are not named, but are referred to by their position in the definition. Let's say you are writing a color conversion/calculation library for use in your graphics application and want to represent RGB color values in code. We can represent our Color type and the related items like so:

// tuple_struct.rs 

struct Color(u8, u8, u8);

fn main() {
let white = Color(255, 255, 255);

// You can pull them out by index
let red = white.0;
let green = white.1;
let blue = white.2;

println!("Red value: {}", red);
println!("Green value: {}", green);
println!("Blue value: {}\n", blue);

let orange = Color(255, 165, 0);

// You can also destructure the fields directly
let Color(r, g, b) = orange;
println!("R: {}, G: {}, B: {} (orange)", r, g, b);

// Can also ignore fields while destructuring
let Color(r, _, b) = orange;
}

In the preceding code, Color(u8, u8, u8) is a tuple struct that was created and stored in white. We then access the individual color components in white using the white.0 syntax. Fields within the tuple struct can be accessed by the variable.<index> syntax, where the index refers to the position of the field in the struct, which starts with 0. Another way to access the individual fields of a struct is by destructuring the struct using the let statement. In the second part, we created a color orange. Following that, we wrote the let statement with Color(r, g, b) on the left-hand side and to the right we put our orange. This results in three fields in orange getting stored within the r, g, and b variables. The types of r, g, and b are also inferred automatically for us.

The tuple struct is an ideal choice when you need to model data that has less than four or five attributes. Anything more than that hinders readability and reasoning. For a data type that has more than three fields cases, it's recommended to use a C-like struct, which is the third form and the most commonly used one. Consider the following code:

// structs.rs

struct Player {
name: String,
iq: u8,
friends: u8,
score: u16
}

fn bump_player_score(mut player: Player, score: u16) {
player.score += 120;
println!("Updated player stats:");
println!("Name: {}", player.name);
println!("IQ: {}", player.iq);
println!("Friends: {}", player.friends);
println!("Score: {}", player.score);
}

fn main() {
let name = "Alice".to_string();
let player = Player { name,
iq: 171,
friends: 134,
score: 1129 };

bump_player_score(player, 120);
}

In the preceding code, structs  are created in the same way as tuple structs, that is, by writing the struct keyword followed by the name of the struct. However, they start with braces and their field declarations are named. Within braces, we can write fields as field: type comma-separated pairs. Creating an instance of a struct is also simple; we write Player, followed by a pair of braces, which contains comma-separated field initializations. When initializing a field from a variable that has the same name as the field name, we can use the field init shorthand feature, which is the case with the name field in the preceding code. We can then access the fields from the created instance easily by using the struct.field_name syntax. In the preceding code, we also have a function called bump_player_score, which takes the struct Player as a parameter. Function arguments are immutable by default, so when we want to modify the score of the player, we need to change the parameter to mut player in our function, which allows us to modify any of its fields. Having a mut on the struct implies mutability for all of its fields.

The advantage of using a struct rather than a tuple struct is that we can initialize the fields in any order. It also allows us to provide meaningful names to the fields. As a side note, the size of a struct is simply the sum of its individual field members, along with any data alignment padding, if required. They don't have any extra metadata size overhead associated with them. Next, let's look at enumerations, also known as enums.