Learning Rust 🦀: 10 - The Struct
Tags: learning, rust, programing, development
Published: 2023-08-18 16:36:38

Now we will have our first look at how we can make our own custom types in Rust. Let's jump in!

>#### ⚠️ Remember!
You can find all the code snippets for this series in its [accompanying repo](https://github.com/FadyGrAb/rust-learning-series)  
If you don't want to install Rust locally, you can play with all the code of this series in the official [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021) that can be found on its official page.

> ⚠️⚠️ The articles in this series are loosely following the contents of "The Rust Programming Language, 2nd Edition" by Steve Klabnik and Carol Nichols in a way that reflects my understanding from a Python developer's perspective.

⭐ I try to publish a new article every week (maybe more if the Rust gods 🙌 are generous 😁) so stay tuned 😉. I'll be posting "new articles updates" on my [LinkedIn](https://www.linkedin.com/in/fady-abdelmassih) and [Twitter](https://twitter.com/fadyGrAb).

## Table of Contents:
- [The basics of Struct](#the-basics-of-struct)
- [Our Rectangle type](#our-Rectangletype)
- [Structs and Methods](#structs-and-methods)
- [Associated functions that are not methods](#associated-functions-that-are-not-methods)


### Struct:
We have looked previously at some Rust types that groups together "related" data like Array (same type) and Tuple (different types). Basically, a Struct - or structure - is a way to group related data with different types but also naming them in a custom type. They are more or less like an Object (or more precisely, a Class) in OOP languages.  
We define a named Struct with the following syntax: 
> Yes, there are unnamed (tuple) or even with only the struct name. but we will see those in later posts.

```rust
struct Car {
    maker: String,
    model: String,
    year_of_making: u32,
    color: String,
}
```

This defines a Struct called `Car` that has named ... attributes - if we can call them that - and their types. This definition is located outside of any function including the `main` function.  
To use the Struct, we have to create an instance of it. We can then access its attributes with the dot `.` notation as follows:
```rust
fn main() {
    // Define and using a Struct
    let car1 = Car {
        model: String::from("model1"),
        maker: String::from("Maker1"),
        color: String::from("Red"),
        year_of_making: 2023,
    };

    println!("Car 1 is a {} and of {} color", car1.maker, car1.color);
}
```
In this code snippet, we have created an immutable `Car` instance and printed out its maker and color. Note the attributes order we used. The instantiation order doesn't have to match the definition order.  
As Struct is considered a type, we can pass and return it to/from functions. One way we can use that is in a **factory** function that returns a new instance of the Struct. We can also use a neat shorthand that Rust provides that we don't have to write the name of the attribute and its variable as long as both have the same name. Meaning that instead of writing `Car { maker: maker }` we can write it like that `Car { maker }` which proves very usefull for Struct with many attributes:
```rust
fn main() {
    .....

    let car2 = get_car(
        String::from("Maker2"),
        String::from("Model2"),
        2022,
        String::from("Yellow"),
    );

    println!("Car 2 is a {} of {} color", car2.maker, car2.color);
}
fn get_car(maker: String, model: String, year_of_making: u32, color: String) -> Car {
    Car {
        maker,
        model,
        year_of_making,
        color,
    }
}
```
Notice the shorthand usage in the `get_car` function.  
We can also create Struct instances from the instances of the same type containing some or all the attributes of the original instance using the `..` syntax. But be careful, as this operation **moves** the original attributes if complex types (heap data) are used i.e. the new instance take ownership. When using the `..`, you can modify some of the original attributes' values and any attribute that wasn't explicitly modified will get its value from the original attributes. The `..` should be written at the end of the Struct instantiation. 
```rust
fn main() {
    // Define and using a Struct
    let car1 = Car {
        model: String::from("model1"),
        maker: String::from("Maker1"),
        color: String::from("Red"),
        year_of_making: 2023,
    };

    let mut car3 = Car {
        color: String::from("Blue"),
        ..car1
    };

    println!("Can we print car1 maker now? {}", car1.maker);  // ERROR: borrow of moved value: `car1.maker`
    println!("Car 3 is a {} and of {} color", car3.maker, car3.color);

    car3.color = String::from("Green");

    println!("Now car 3 is of {} color", car3.color);

}
```
Note that the last `println!` macro will cause a compile time error as `car1.maker` was **moved** into `car3.maker`. Also, if the Struct is mutable, we can modify its values with the dot `.` notation.
> We can "**borrow**" the values and not move them between instances, but this will require the use of "**lifetimes**" which are a topic for another article 😉.

### Our Rectangle type:

We will now start building our `Rectangle` type (Struct). It will contain two main attributes, the `width` and the `height`. We can also create a function to calculate its area taking a `Rectangle` as input:
```rust
struct Rectangle {
    height: u32,
    width: u32,
}
fn main() {
    let rect1 = Rectangle {
        height: 10,
        width: 5,
    };
    println!("rect1 is {} x {} and its area is: {}", rect1.width, rect1.height, area(&rect1);
}

fn area(rect: &Rectangle) -> u32 {
    rect.width * rect.height
}
```
For this particular case, we could have used a tuple and accessed its width and height like this:
```rust
let rect1 = (5, 10);
println!("rect1 is {} x {}", rect1.0, rect1.1);
....
```
But the Struct one is cleaner! With the Tuple, it isn't clear which value is the width and which is the height. We rely on the order of the items in the tuple which will make our code harder to read for anyone trying to use it!

### Structs and Methods:
Our `area` function is very specific to the `Rectangle` type. We can "**associate**" it with the Struct with the `impl` (implementations) keyword. If we passed `&self` to this function, it will become a method (Yup! this weird naming scheme again 🤷‍♂️). So, let's do that:
> `&self` is a shorthand for `self: &Self`. The `Self` type is an alias of the type that is in the `impl` block. We call them "**associated**" functions as they are "**associated**" with the type.

```rust
struct Rectangle {
    height: u32,
    width: u32,
}
impl Rectangle {
    fn area(&self) -> u32 {
        self.height * self. width
    }
}

fn main() {
    let rect1 = Rectangle {
        height: 10,
        width: 5,
    };
    println!("rect1 is {} x {}", rect1.width, rect1.height);
    println!("rect1 area is: {}", rect1.area());
}
```
We can also create methods that compare the owner instance with other instances of the same type. Like for example, we can write a method that determines if a rectangle can fit other ones comparing their width and height:
```rust
struct Rectangle {
    height: u32,
    width: u32,
}
impl Rectangle {
    fn area(&self) -> u32 {
        self.height * self.width
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        (self.width >= other.width) & (self.height >= other. height)
    }
}

fn main() {
    let rect1 = Rectangle {
        height: 10,
        width: 5,
    };
    println!("rect1 is {} x {}", rect1.width, rect1.height);
    println!("rect1 area is: {}", rect1.area());

    let rect2 = Rectangle {
        height: 9,
        width: 4,
    };

    let rect3 = Rectangle {
        height: 11,
        width: 6,
    };

    println!("Can rect1 hold rect2: {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3: {}", rect1.can_hold(&rect3));

}
```
With the `can_hold` method, we are passing a borrow of another rectangle and see if our owner rectangle can fit those. The output will be `true` and `false` for the last two prints.

### Associated functions that are not methods:

We can write associated functions (or methods or call them whatever you like 😁) in the `impl` that doesn't receive `&self` as a parameter (or whatever you call them). In this case, we can call them with the `::` notation (like in the `String::new()` but this one is module "namespacing" which can also be used with Structs)
```rust
struct Rectangle {
    height: u32,
    width: u32,
}
impl Rectangle {
    ....
    }
    fn square(dimension: u32) -> Rectangle {
        Rectangle {
            width: dimension,
            height: dimension,
        }
    }
}

fn main() {
  
    let square1 = Rectangle::square(5);

    println!("square1 is {} x {}", square1.width, square1.height);
}
```
Here, `square` function is just returning a special `Rectangle` with both its width and height are equal to the passed value to it.

In the next article, we will talk about how we can debug our custom types! See you then 👋.