βοΈ Structs are used to encapsulate related properties into one unified data type.
π‘ By convention, the name of the struct should follow PascalCase.
There are 3 variants of structs,
- C-like structs
- One or more comma-separated name:value pairs
- Brace-enclosed list
- Similar to classes (without its methods) in OOP languages
- Because fields have names, we can access them through dot notation
- Tuple structs
- One or more comma-separated values
- A parenthesized list like tuples
- Looks like a named tuples
- Unit structs
- A struct with no members at all
- It defines a new type but it resembles an empty tuple, ()
- Rarely in use, useful with generics
βοΈ When regarding OOP in Rust, attributes and methods are placed separately on structs and traits. Structs contain only attributes, traits contain only methods. They are getting connected via impls.
π‘More complex examples can be found on impls & traits, lifetimes and modules sections.
C-like structs
// Struct Declaration
struct Color {
red: u8,
green: u8,
blue: u8
}
fn main() {
// Creating an instance
let black = Color {red: 0, green: 0, blue: 0};
// Accessing its fields using dot notation
println!("Black = rgb({}, {}, {})", black.red, black.green, black.blue); //Black = rgb(0, 0, 0)
// Structs are immutable by default, use `mut` to make it mutable but doesn't support field level mutability
let mut link_color = Color {red: 0,green: 0,blue: 255};
link_color.blue = 238;
println!("Link Color = rgb({}, {}, {})", link_color.red, link_color.green, link_color.blue); //Link Color = rgb(0, 0, 238)
// Copy elements from another instance
let blue = Color {blue: 255, .. link_color};
println!("Blue = rgb({}, {}, {})", blue.red, blue.green, blue.blue); //Blue = rgb(0, 0, 255)
// Destructure the instance using a `let` binding, this will not destruct blue instance
let Color {red: r, green: g, blue: b} = blue;
println!("Blue = rgb({}, {}, {})", r, g, b); //Blue = rgb(0, 0, 255)
// Creating an instance via functions & accessing its fields
let midnightblue = get_midnightblue_color();
println!("Midnight Blue = rgb({}, {}, {})", midnightblue.red, midnightblue.green, midnightblue.blue); //Midnight Blue = rgb(25, 25, 112)
// Destructure the instance using a `let` binding
let Color {red: r, green: g, blue: b} = get_midnightblue_color();
println!("Midnight Blue = rgb({}, {}, {})", r, g, b); //Midnight Blue = rgb(25, 25, 112)
}
fn get_midnightblue_color() -> Color {
Color {red: 25, green: 25, blue: 112}
}
Tuple structs
βοΈ When a tuple struct has only one element, we call it newtype pattern. Because it helps to create a new type.
struct Color(u8, u8, u8);
struct Kilometers(i32);
fn main() {
// Creating an instance
let black = Color(0, 0, 0);
// Destructure the instance using a `let` binding, this will not destruct black instance
let Color(r, g, b) = black;
println!("Black = rgb({}, {}, {})", r, g, b); //black = rgb(0, 0, 0);
// Newtype pattern
let distance = Kilometers(20);
// Destructure the instance using a `let` binding
let Kilometers(distance_in_km) = distance;
println!("The distance: {} km", distance_in_km); //The distance: 20 km
}
Unit structs
This is rarely useful on its own. But in combination with other features, it can become useful.
π ex: A library may ask you to create a structure that implements a certain trait to handle events. If you donβt have any data you need to store in the structure, you can create a unit-like struct.
struct Electron;
fn main() {
let x = Electron;
}