Rust: structs, methods, and traits
Tags: rust, beginners, showdev, tutorial
Published: 2020-01-22 13:50:04

This blog offers a quick tour of Rust `struct`s, `method`s and `trait`s. It uses simple examples to demonstrate the concepts.

> the code is available on [GitHub](https://github.com/abhirockzz/learning-rust)

## struct

A `struct` in Rust is the same as a `Class` in Java or a `struct` in Golang. Its a named type to which you can assign *state* (attributes/fields) and *behavior* (methods/functions).

Here is a `struct` with fields

```
struct Programmer {
    email: String,
    github: String,
    blog: String,
}
```

To instantiate a `Programmer`, you can simply:

```
let pg1 = Programmer {
        email: String::from("abhirockzz@gmail.com"),
        github: String::from("https://github.com/abhirockzz"),
        blog: String::from("https://dev.to/abhirockzz"),
    };
```

## Methods

Methods are *behavior* associated with a given type. The first parameter in a method is always `self`, which represents the instance on which the method is being invoked.

Let's add a method to `Programmer`. To do that, we will need use an `impl` block:

```
impl Programmer {
    fn is_same_as(&self, other: Programmer) -> bool {
        return self.email == other.email;
    }
}
```

The `is_same_as` method accepts a reference to the instance being invoked on (`&self`) and another `Programmer` instance. To call it, create another instance of a `Programmer` (`pg2`) and compare `pg1` with it.

```
let pg2 = Programmer {
        email: String::from("abhirockzz@gmail.com"),
        github: String::from("https://github.com/abhirockzz"),
        blog: String::from("https://medium.com/@abhishek1987"),
    };

println!("pg1 same as pg2? {}", pg1.is_same_as(pg2));
```
### **self** types

We used `&self` as the parameter for `is_same_as`. This way, we will only pass a reference and the function will *not* own the value - only borrow it (see [Rust: Ownership and Borrowing](https://dev.to/itnext/rust-ownership-and-borrowing-i2n)). It is also possible to use `self` and `&mut self`.

You can use `self`, but be careful that it will pass on the *ownership* to the function. Let's see this function

```
fn details(self) {
        println!(
            "Email: {},\nGitHub repo: {},\nBlog: {}",
            self.email, self.github, self.blog
        )
}
```

You can invoke it using the `pg2` instance as such `pg2.details();` and you should get back

```
Email: abhirockzz@gmail.com,
GitHub repo: https://github.com/abhirockzz,
Blog: https://medium.com/@abhishek1987
```

If you try to use `pg2` again (e.g. `pg2.is_same_as(&pg1);`), you will get a compiler error 

```
error[E0382]: borrow of moved value: `pg2`
```

If you want to mutate the `Programmer` instance, make use of `&mut self` as follows:

```
fn some_function(&mut self) {
    //use self
}

//to invoke it
let mut pg3 = Programmer{...};
pg3.some_function();
```

If you don't mark the variable (`pg3`) as `mut` (mutable), you'll get a compiler error

```
error[E0596]: cannot borrow `pg2` as mutable, as it is not declared as mutable
```

### Other functions related to the struct

You can add `associated functions` which are tied with the instance of a `struct` - think of it as a `static` method in Java. These are commonly used as constructors

> it's different to how constructors are used in Java but similar to Go approach

```
fn new(email: String, github: String, blog: String) -> Self {
    return Programmer {
        email: email,
        github: github,
        blog: blog,
    };
}
```

Here is how you can use it:

```
let pg3 = Programmer::new(
        String::from("abhirockzz@gmail.com"),
        String::from("https://github.com/abhirockzz"),
        String::from("https://medium.com/@abhishek1987"),
    );
```

Notice that we are using `::` (`Programmer::new`) to access `associated` members of a `struct` (a function in this case)

> It is possible to use *different* `impl` blocks for the same `struct`

## Trait

A `Trait` in Rust is similar to `Interface` in other languages such as Java etc. They help define one or more set of behaviors which can be `implemented` by different types in their own unique way. The way a `Trait` is implemented in Rust is quite similar to how it's done in Java. In Java, you can use the `implements` keyword, which Rust uses `impl`

> There is an explicit association b/w the interface and the type implementing it. This is quite different compared to Go, where you don't need to declare which interface you're implementing - if you have implemented the required behavior, the compiler will be happy.

Let's start by defining a `Trait`

```
trait PrettyPrint {
    fn pretty_print(&self);
}
```

.. and implementing it 

> don't worry about the specifics of the example, just focus on the key parts

```
impl PrettyPrint for Programmer {
    fn pretty_print(&self) {
        println!(
            "{{\n\t\"email\": {},\n\t\"github_repo\" repo: {},\n\t\"blog_url\": {}\n}}",
            self.email, self.github, self.blog
        )
    }
}
```

It's quite simple - use the `impl` keyword followed by the trait you're implementing and include the type which is implementing the trait after the `for` keyword. Now you can use it just like any other method:

```
let pg = Programmer::new(...);
pg.pretty_print();
```

### Use a `std` library trait

Rust standard library provides a number of `Trait`s which you can use. Let's implement the [`std::fmt::Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html) trait. This is for user-facing output e.g. we can use it with the `println!` macro

```
impl std::fmt::Display for Programmer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "({}, {}, {})", self.email, self.github, self.blog)
    }
}
```

> Don't worry about the specifics of the implementation and the types such as `std::fmt::Formatter` etc. For now, just understand that you can implement `Display` trait from the Rust `std::fmt` module.

From now on, you can use an instance of a `Programmer` with `println!` as such

```
let another_pg = Programmer::new(...);
println!("programmer details: {}", another_pg);

//output:
programmer details: (abhirockzz@gmail.com, https://github.com/abhirockzz, https://medium.com/@abhishek1987)
```

### Freebies!

Rust provides many useful traits that you can use for free! This means that you *don't* need to implement them explicitly (you certainly can if you want to). The way you do it is by using the `#[derive]` attribute.

> An `attribute` is just a piece of metadata that you can apply to structs etc. They remind me of Java annotations

Here is an example. We can use apply the `std::fmt::Debug` trait on `Programmer` and then use it with `println!`. This is similar to what we did with `Display` but the key difference is that its not possible to `derive` the `Display` (you *have* to implement it). All you need to do is add `#[derive(Debug)]` to the the `Programmer` struct:

```
#[derive(Debug)]
struct Programmer {
    email: String,
    github: String,
    blog: String,
}
```

Simply use the `:?` format to leverage the default `Debug` functionality

```
let pg = Programmer::new(...);
println!("{:?}", pg);

//output:
Programmer { email: "abhirockzz@gmail.com", github: "https://github.com/abhirockzz", blog: "https://medium.com/@abhishek1987" }
```

If you add a `#` to the mix, you get pretty printing for free.. yay!

```
println!("{:#?}", pg);

//output
Programmer {
    email: "abhirockzz@gmail.com",
    github: "https://github.com/abhirockzz",
    blog: "https://medium.com/@abhishek1987",
}
```

> There are other utility traits such as `Eq`, `Clone`, `PartialEq` etc. which you can `derive`

### Using traits

To take advantage of traits, you should be able to accept and return them from functions and methods in order to make use of the "general" behavior. We can write a function which accepts a type that implements `PrettyPrint`

```
fn print_the_printable(p: impl PrettyPrint) {
    p.pretty_print()
}

//invoke
let pg = Programmer::new(...);
print_the_printable(pg);
```

> note that `impl` has been added to the parameter

A `PrettyPrint` can be returned as well

```
fn get_printable(info: Vec<String>) -> impl PrettyPrint {
    Programmer::new(
        String::from(&info[0]),
        String::from(&info[1]),
        String::from(&info[2]),
    )
}

//invoke
let info: Vec<String> = vec![
        String::from("abhirockzz@gmail.com"),
        String::from("https://github.com/abhirockzz"),
        String::from("https://medium.com/@abhishek1987"),
    ];
get_printable(info).pretty_print();
```

> **Default methods**

> It is possible to provide default implementations of methods within the trait itself (these can be overridden if required). These methods can also invoke other trait methods (default or not)