Rust structs are user-defined types which combine an arbitrary number of values under a new type. They are the building blocks used to model an application's domain.
Rust has three kinds of structs, all created using the struct
keyword:
- Structs with named fields
- Tuple structs
- Unit structs
Structs with named fields
A struct with named fields is created using the struct
keyword, followed by the struct name and a list of fields tagged with their respective data types. A board game struct could be defined this way:
struct BoardGame {
name: String,
min_players: u8,
max_players: u8,
publisher: String
}
This is how we can create an instance of the BoardGame
struct and access its fields:
let mut board_game_1 = BoardGame {
name: String::from("Agricola"),
min_players: 1,
max_players: 5,
publisher: String::from("Mayfair Games")
};
board_game_1.publisher = String::from("New publisher name");
Note that in order to mutate the instance it is necessary to make it mutable explicitly with let mut
.
Field init shorthand syntax
When initialising a struct whose field names match the parameter values being used, Rust provides a shorthand syntax to make this more pleasant to write. Let's extend the previous example to see this scenario in action:
let min_players: u8 = 1;
let max_players: u8 = 5;
let board_game_1 = BoardGame {
name: String::from("Agricola"),
min_players: min_players,
max_players: max_players,
publisher: String::from("Mayfair Games")
};
This could be simplified using the field init shorthand syntax, which avoids having to repeat min_players
and max_players
for both the struct fields and their parameters.
let min_players: u8 = 1;
let max_players: u8 = 5;
let board_game_1 = BoardGame {
name: String::from("Agricola"),
min_players, // instead of min_players: min_players
max_players, // instead of max_players: max_players
publisher: String::from("Mayfair Games")
};
Struct update syntax
Another common scenario when dealing with structs is wanting to initialise an instance of a struct based on another instance. Again, let's extend the initial example, this time to create a second board game instance based on the original one:
let board_game_1 = BoardGame {
name: String::from("Agricola"),
min_players: min_players,
max_players: max_players,
publisher: String::from("Mayfair Games")
};
let board_game_2 = BoardGame {
name: String::from("Agricola Revised Edition"),
min_players: board_game_1.min_players,
max_players: board_game_1.max_players,
publisher: board_game_1.publisher
};
This can become quite tedious, especially when most fields remain the same. To help with this, we can use the struct update syntax:
let board_game_1 = BoardGame {
name: String::from("Agricola"),
min_players: min_players,
max_players: max_players,
publisher: String::from("Mayfair Games")
};
let board_game_2 = BoardGame {
name: String::from("Agricola Revised Edition"),
..board_game_1
};
The only property that is different from the original instance was the name. The ..board_game_1
syntax ensures the remaining fields on board_game_2
are initialised taking their values from board_game_1
.
Tuple structs
Tuple structs are very similar to tuples. Both are created the same way, however tuple structs must have a name:
struct Product(String, f64);
let product = Product(String::from("MacBook Pro"), 1499.99);
The values of a tuple struct can be accessed just like tuples:
let product = Product(String::from("MacBook Pro"), 1499.99);
let product_name = product.0;
let product_price = product.1;
Unit structs
The final struct type, unit structs, contain no fields:
struct EmptyResponse;
let response = EmptyResponse;
Unit structs resemble the unit type ()
and can be useful in some situations, for example with generics.
Methods and traits
Structs can contain methods:
impl BoardGame {
fn can_be_played_alone(&self) -> bool {
self.min_players == 1
}
}
let board_game_1 = BoardGame {
name: String::from("Catan"),
min_players: 3,
max_players: 5,
publisher: String::from("Publisher")
};
if board_game_1.can_be_played_alone() { // false
println!("Can be played alone!");
} else {
println!("Minimum of {} people!", board_game_1.min_players);
}
And can implement traits:
trait IdentifiableGame {
fn game_identifier(&self) -> String;
}
impl IdentifiableGame for BoardGame {
fn game_identifier(&self) -> String {
format!("{} by {}", self.name, self.publisher)
}
}
println!("{}", board_game_1.game_identifier());