Home Rust Tutorial: Structs in Rust Programming Language

Tutorial: Structs in Rust Programming Language

Structures (structs) in Rust are user-defined types that allow you to group related data together.

They are similar to classes in object-oriented programming but without methods or inheritance.

Structs make your code more organized, readable, and maintainable.

What You’ll Learn

1. What Are Structs in Rust?

A struct in Rust is a way to create custom data types by grouping fields together. Each field can have a specific type and name.

Syntax: Defining a Struct

struct StructName {
    field1: Type,
    field2: Type,
    // Additional fields
}

2. Defining and Instantiating Structs

Example: Defining and Creating an Instance

struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };

    println!("Rectangle: {} x {}", rect.width, rect.height);
}

Example: Mutable Struct Instance

Struct instances can be mutable, allowing modification of fields.

fn main() {
    let mut rect = Rectangle {
        width: 30,
        height: 50,
    };

    rect.width = 40; // Modify the width
    println!("Updated Rectangle: {} x {}", rect.width, rect.height);
}

Example: Using Field Init Shorthand

If variable names match field names, you can use shorthand.

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let x = 10;
    let y = 20;

    let point = Point { x, y }; // Shorthand for `x: x, y: y`
    println!("Point: ({}, {})", point.x, point.y);
}

3. Accessing and Modifying Fields

Accessing Fields

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };

    println!("Width: {}", rect.width);
    println!("Height: {}", rect.height);
}

Destructuring Structs

fn main() {
    let point = Point { x: 10, y: 20 };
    let Point { x, y } = point; // Destructure fields

    println!("x: {}, y: {}", x, y);
}

4. Using impl to Add Methods

You can add methods to structs using impl.

Example: Adding Methods

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn is_square(&self) -> bool {
        self.width == self.height
    }
}

fn main() {
    let rect = Rectangle {
        width: 30,
        height: 50,
    };

    println!("Area: {}", rect.area());
    println!("Is square? {}", rect.is_square());
}

Example: Associated Functions

Associated functions do not take self as a parameter and are typically used as constructors.

impl Rectangle {
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }
}

fn main() {
    let rect = Rectangle::new(40, 60);
    println!("Rectangle: {} x {}", rect.width, rect.height);
}

5. Tuple Structs

Tuple structs are structs without named fields, making them useful for simple groupings.

Example: Tuple Struct

struct Color(u8, u8, u8);

fn main() {
    let red = Color(255, 0, 0);
    println!("Red: ({}, {}, {})", red.0, red.1, red.2);
}

6. Unit-like Structs

Unit-like structs are structs without fields. They are used for types with no data, like markers.

Example: Unit-like Struct

struct Marker;

fn main() {
    let _marker = Marker;
    println!("Marker struct created.");
}

7. Structs with Default Values

You can derive the Default trait to create structs with default values.

Example: Using Default Trait

#[derive(Default)]
struct Config {
    width: u32,
    height: u32,
    fullscreen: bool,
}

fn main() {
    let default_config = Config::default();
    println!(
        "Default Config: {}x{}, Fullscreen: {}",
        default_config.width, default_config.height, default_config.fullscreen
    );
}

Example: Custom Defaults

impl Default for Config {
    fn default() -> Self {
        Config {
            width: 800,
            height: 600,
            fullscreen: false,
        }
    }
}

fn main() {
    let config = Config::default();
    println!(
        "Custom Config: {}x{}, Fullscreen: {}",
        config.width, config.height, config.fullscreen
    );
}

8. Practical Examples

8.1 Struct for 2D Points

struct Point {
    x: f64,
    y: f64,
}

impl Point {
    fn distance(&self, other: &Point) -> f64 {
        ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
    }
}

fn main() {
    let p1 = Point { x: 0.0, y: 0.0 };
    let p2 = Point { x: 3.0, y: 4.0 };

    println!("Distance: {}", p1.distance(&p2));
}

8.2 Struct for RGB Colors

struct Color {
    red: u8,
    green: u8,
    blue: u8,
}

impl Color {
    fn hex(&self) -> String {
        format!("#{:02X}{:02X}{:02X}", self.red, self.green, self.blue)
    }
}

fn main() {
    let color = Color {
        red: 255,
        green: 165,
        blue: 0,
    };

    println!("Color Hex: {}", color.hex());
}

8.3 Struct for Configurations

#[derive(Default)]
struct AppConfig {
    debug: bool,
    max_threads: u32,
    app_name: String,
}

fn main() {
    let mut config = AppConfig {
        debug: true,
        ..Default::default() // Use default values for other fields
    };

    config.app_name = String::from("Rusty App");
    println!(
        "App Name: {}, Debug: {}, Max Threads: {}",
        config.app_name, config.debug, config.max_threads
    );
}

8.4 Struct for Bank Accounts

struct BankAccount {
    balance: f64,
}

impl BankAccount {
    fn deposit(&mut self, amount: f64) {
        self.balance += amount;
    }

    fn withdraw(&mut self, amount: f64) -> bool {
        if self.balance >= amount {
            self.balance -= amount;
            true
        } else {
            false
        }
    }
}

fn main() {
    let mut account = BankAccount { balance: 1000.0 };

    account.deposit(500.0);
    println!("Balance: {}", account.balance);

    if account.withdraw(300.0) {
        println!("Withdrawal successful. Balance: {}", account.balance);
    } else {
        println!("Not enough funds.");
    }
}

9. Summary

Key Features

  • Named Structs: Group related data with named fields.
  • Tuple Structs: Group data without field names.
  • Unit-like Structs: Used for types without data.
  • Default Values: Easily set defaults for struct fields.

Best Practices

  • Use structs to encapsulate related data.
  • Add methods using impl to improve usability.
  • Use Default to simplify initialization.

Rust structs are versatile and a core feature of building efficient, modular applications

You may also like