Lesson 1
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:

Rust
1struct Point { 2 x: i32, 3 y: i32, 4}

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:

Rust
1// Tuple struct 2struct Color(i32, i32, i32); // RGB color 3let black = Color(0, 0, 0); 4 5// Unit structs 6struct Idle; // System is in idle state 7struct Active; // System is in active state
Creating and Using Struct Instances

To create an instance of our Point struct:

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

You can access struct fields using dot notation:

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

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

Rust
1let 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:

Rust
1impl Point { 2 // Associated function (similar to static methods in other languages) 3 // Called using Point::new(x, y) 4 fn new(x: i32, y: i32) -> Self { 5 Self { x, y } 6 } 7 8 // Method that operates on an instance (like instance methods in other languages) 9 // Called using point.distance_from_origin() 10 fn distance_from_origin(&self) -> f64 { 11 ((self.x.pow(2) + self.y.pow(2)) as f64).sqrt() 12 } 13}

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.

Applying Structs in a Rust Program

Here's how you can use the Point struct and the methods we've implemented in a Rust program:

Rust
1fn main() { 2 let point = Point::new(3, 4); 3 println!("Distance from origin: {}", point.distance_from_origin()); 4} // Output: Distance from origin: 5

In this program:

  • We create a Point instance using the new method.
  • We calculate the distance from the origin using distance_from_origin.
  • The println! macro is then used to display the result.

This demonstrates how using structs and methods effectively can help structure and simplify your program logic.

Summary and Preparation for Practice

In this lesson, we explored how to define and use structs in Rust, including creating instances and implementing methods. We covered regular structs, tuple structs, and unit structs, along with field access and update syntax. We used a Point struct to illustrate these concepts, showing how to encapsulate data and functionality within a structured format. This foundational understanding prepares you to tackle more complex data structures and design patterns in future lessons.

As you move on to the practice exercises, focus on applying these concepts by creating and manipulating your own structs. These exercises will reinforce your understanding and help you become more comfortable using structs in your Rust programs. Good luck, and enjoy the journey of mastering Rust design patterns!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.