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:
In this example:
struct Pointdefines a new struct namedPoint.- Inside the curly braces
{}, we define the fieldsxandy, both of which are of typei32, 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:
Creating and Using Struct Instances
To create an instance of our Point struct:
You can access struct fields using dot notation:
Rust also provides a convenient struct update syntax to create a new instance based on an existing one:
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:
- Methods are functions that operate on an instance of the struct and take
selfas their first parameter, which is used to access the struct's data. - Associated functions are functions that don't take
selfas 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:
In this implementation:
newis an associated function called usingPoint::new(x, y)that creates and returns an instance ofPoint. It doesn't need an instance to work with, which is why it doesn't takeself. Please note that usingnewas a constructor is a common convention in Rust, but you can actually use any name for associated functions that create instances.distance_from_originis a method that takes&self(a reference to the instance). It can access the instance's data throughself.xandself.y.Selfis an alias for the type in theimplblock (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.
