Structs

⭐️ 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,

  1. 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
  1. Tuple structs
  • One or more comma-separated values
  • A parenthesized list like tuples
  • Looks like a named tuples
  1. 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;
}