Structs and Implementations in Rust

Introduction

Hello there, welcome to the very first lesson of this course on Fundamental Rust Concepts for Design Patterns! In this starting lesson, we will dive into the concept of structs in Rust. Structs, or structures, play a vital role in Rust's ability to organize data efficiently, allowing you to create custom data types that enhance code readability and maintain a clean architecture. Structs help form the building blocks for complex systems in Rust, and understanding them will pave the way for mastering more advanced design patterns later in the course.

Defining Structs

Let's start by defining a struct in Rust. A struct is essentially a way to group related data together. Think of it as creating a new data type that can have multiple pieces of data, called fields, associated with it.

Here's how you can define a simple struct to represent a Point in a 2D space:

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

In this example:

  • struct Point defines a new struct named Point.
  • Inside the curly braces {}, we define the fields x and y, both of which are of type i32, representing integer coordinates of the point in a 2-dimensional plane.

Rust also supports tuple structs, which are struct variants without named fields, and unit structs, which have no fields at all and are useful when implementing traits:

// Tuple struct
struct Color(i32, i32, i32); // RGB color
let black = Color(0, 0, 0);

// Unit structs
struct Idle;    // System is in idle state
struct Active;  // System is in active state

Creating and Using Struct Instances

To create an instance of our Point struct:

let point = Point { x: 3, y: 4 };

You can access struct fields using dot notation:

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

Rust also provides a convenient struct update syntax to create a new instance based on an existing one:

let point2 = Point { x: 5, ..point }; // y will be 4

Implementing Methods for Structs

To extend the functionality of structs, Rust allows you to implement both methods and associated functions. Both are defined within impl blocks, but they serve different purposes:

  1. Methods are functions that operate on an instance of the struct and take self as their first parameter, which is used to access the struct's data.
  2. Associated functions are functions that don't take self as a parameter: they're associated with the type itself rather than any particular instance. For example, constructors are usually implemented as associated functions.

Let's see both in action with our Point struct:

impl Point {
    // Associated function (similar to static methods in other languages)
    // Called using Point::new(x, y)
    fn new(x: i32, y: i32) -> Self {
        Self { x, y }
    }

    // Method that operates on an instance (like instance methods in other languages)
    // Called using point.distance_from_origin()
    fn distance_from_origin(&self) -> f64 {
        ((self.x.pow(2) + self.y.pow(2)) as f64).sqrt()
    }
}

In this implementation:

  • new is an associated function called using Point::new(x, y) that creates and returns an instance of Point. It doesn't need an instance to work with, which is why it doesn't take self. Please note that using new as a constructor is a common convention in Rust, but you can actually use any name for associated functions that create instances.
  • distance_from_origin is a method that takes &self (a reference to the instance). It can access the instance's data through self.x and self.y.
  • Self is an alias for the type in the impl block (in this case, Point).

Methods can take self in three different ways:

  • self: Takes ownership of the instance, giving the method full control but consuming the instance
  • &self: Immutably borrows the instance, allowing the method to read but not modify the data
  • &mut self: Mutably borrows the instance, allowing the method to both read and modify the data

In our example, distance_from_origin only needs to read the values, so it borrows self immutably with &self If these terms about ownership and borrowing seem a bit confusing right now, don't worry! We'll be covering these core Rust concepts in an upcoming lesson.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal