Rust Trait Implementation Wizardry 🧙: Unveiling the Magic of Macro Rules
Tags: rust, programming, tutorial, learning
Published: 2023-05-21 19:49:23

> IMPORTANT: This article will be a continuation of my previous article about rust traits. So go quickly read it first if you need to understand rust traits. [The best TRAIT of RUST 🔥 (no pun intended)](https://dev.to/cleggacus/the-best-trait-of-rust-no-pun-intended-2516)

## Introduction
If you have ever used rust it may seem like you have to rewrite a lot of code due to how the only way you can add repeatable functionality is through composing your structs with traits. Well lucky for you there is this amazing feature called macros.

## What is a macro in rust?

Macros can help use write a function that generates code at compile time. Using macros can make you a much faster programmer since you can write a macro that will implement traits for you meaning you don't have to keep doing the tedious task of rewriting your implementation over and over again.

There are 2 main types of macros in rust: declarative and procedural. Declarative macros in rust are usually referred to as macro rules since they are created but typing `macro_rules!`. On the other hand procedural macros are used for directly accessing the token stream to consume and produce syntax in rust. Procedural macros are used through the `proc-macro` crate and are not going to be the focus of this article.

For this article we will be using macro rules since they are builtin without having to add any crates and are much simpler to create.

## Let's start coding!

For this example we will be continuing from the last article about rust traits. If you wish to follow along you can grab the code from GitHub: [Best trait of rust repository](https://github.com/cleggacus/rust-traits-example/tree/1_best_trait_of_rust)

## What is a number?

So far we have said that to create a Vec2 for type T it must implement the Add trait. This is so that Vec2 can be also used for addition. Since we need lots more operations we can define a number trait to make things a little easier.

```rust
pub trait Number: 
    Clone +
    Copy +
    Sized + 
    Add<Output = Self> + 
    Sub<Output = Self> + 
    Div<Output = Self> + 
    Mul<Output = Self> + 
    AddAssign + 
    SubAssign + 
    DivAssign + 
    MulAssign {}
```

This trait might not have any direct functionality but it does mean when ever we want to say that a generic has all the other necessary trait we just have to say it implements number.

This now means that we will need to implement number for all the number types. The first thought might be to write it out like this.

```rust
impl Number for f32 {}
impl Number for f64 {}
impl Number for u32 {}
impl Number for u64 {}
impl Number for u128 {}
impl Number for i32 {}
impl Number for i64 {}
impl Number for i128 {}
```

Now this isn't to bad and it might be the way you would prefer to write your code since it is more verbose. But for the sake of this article we will make this less verbose through macro rules.

First we think of how we wish the macro rule to look. To make the code super simple we can have a macro rule named impl_number with a list of types that will implement number. It will look something like this.

```rust
impl_number!(usize, f32, f64, u32, u64, u128, i32, i64, i128);
```

To create a macro we start with `macro_rules!` followed by the name of the macro `impl_number`.

```rust
macro_rules! impl_number {
    // code goes here
}
```

We now need need to actually create a the code to generate the line `impl Number for type_name_here {}`.

To do this we create a function that takes the designator ty (which is used for types). A designator is basically just the type of thing you are passing to the macro. The matcher will be written as `$t:ty` where `$t` is the argument name and `ty` is the designator type.

We can then simply write in the function the code we wish to generate. So in this case `impl Number for $t {}`. To impliment a single type for a trait would look like this.

```rust
macro_rules! impl_number {
    ($t:ty) => {
        impl Number for $t {}
    };
}
```

So this is great and all but this only implements one type. To allow a list of arguments to be passed we need surround the matcher with `$(...),+`. If we wish to repeat the generated code for each argument we can surround the code with `$(...)*`. All together this will look like the following.

```rust
macro_rules! impl_number {
    ($($t:ty),+) => {
        $(impl Number for $t {})*
    };
}
```

We can now below this write the following to actually implement the types.

```rust
impl_number!(usize, f32, f64, u32, u64, u128, i32, i64, i128);
```

Now we can rewrite the Vec2 struct from this:

```rust

pub struct Vec2<T> 
where
    T: Add<Output = T> + Copy + Clone
{
    pub x: T,
    pub y: T
}
```

To this:

```rust
#[derive(Debug, Copy, Clone)]
pub struct Vec2<T: Number>  {
    pub x: T,
    pub y: T
}
```

## Operations
There is mainly 2 operations we wish to implement for the Vec2 struct. The first is operators that create a new value and then there's operators that also assign the value. In the previous article we looked at how the implement the Add trait and used the following code:

```rust
impl<T> Add for Vec2<T> 
where
    T: Add<Output = T> + Copy + Clone
{
    type Output = Vec2<T>;

    fn add(self, rhs: Self) -> Self::Output {
        Vec2 {
            x: self.x + rhs.x, 
            y: self.y + rhs.y
        }
    }
}
```

If we look at this implementation block there are only a couple things that change between operators. The trait name changes and the function name changes. Although the operator symbol also changes we can just call the method directly instead.

To implement this as a macro we will use the name impl_vec2_op. This will take 2 designators: an identifier for the trait and an identifier for the method name. An identifier is represented by the keyword ident. We can then replace the trait name with `$trait` and the method name with `$func`. The completed macro rule looks like the following.

```rust
macro_rules! impl_vec2_op {
    ($trait:ident, $func:ident) => {
        impl<T: Number> $trait for Vec2<T> {
            type Output = Self;

            fn $func(self, rhs: Self) -> Self::Output {
                Vec2 {
                    x: self.x.$func(rhs.x),
                    y: self.y.$func(rhs.y)
                }

            }
        }
    };
}
```

We then run the macro with add, sub, div and mul with the following lines.

```rust
impl_vec2_op!(Add, add);
impl_vec2_op!(Sub, sub);
impl_vec2_op!(Div, div);
impl_vec2_op!(Mul, mul);
```

To show the benefits to this macro based approach lets say we now wish to allow adding a number `T` to a vector `Vec2<T>`. We can easily add the implementation block to the macro so that it is implemented for all the operations.

```rust
impl<T:Number> $trait<T> for Vec2<T> {
    type Output = Self;

    fn $func(self, rhs: T) -> Self::Output {
        Vec2 {
            x: self.x.$func(rhs),
            y: self.y.$func(rhs)
        }
    }
}
```

Altogether the macro would look like this:

```rust
macro_rules! impl_vec2_op {
    ($trait:ident, $func:ident) => {
        impl<T: Number> $trait for Vec2<T> {
            type Output = Self;

            fn $func(self, rhs: Self) -> Self::Output {
                Vec2 {
                    x: self.x.$func(rhs.x),
                    y: self.y.$func(rhs.y)
                }
            }
        }

        impl<T:Number> $trait<T> for Vec2<T> {
            type Output = Self;

            fn $func(self, rhs: T) -> Self::Output {
                Vec2 {
                    x: self.x.$func(rhs),
                    y: self.y.$func(rhs)
                }
            }
        }
    };
}
```

We can now similarly do this for the assignment operators with the following macro rule. 

```rust
macro_rules! impl_vec2_op_assign {
    ($trait:ident, $func:ident) => {        
        impl<T: Number> $trait for Vec2<T> {
            fn $func(&mut self, rhs: Self) {
                self.x.$func(rhs.x);
                self.y.$func(rhs.y);
            }
        }

        impl<T: Number> $trait<T> for Vec2<T> {
            fn $func(&mut self, rhs: T) {
                self.x.$func(rhs);
                self.y.$func(rhs);
            }
        }
    };
}

impl_vec2_op_assign!(AddAssign, add_assign);
impl_vec2_op_assign!(SubAssign, sub_assign);
impl_vec2_op_assign!(DivAssign, div_assign);
impl_vec2_op_assign!(MulAssign, mul_assign);
```

Finally we can update our ToVec2 trait. For this we will instead use the From trait since it is included in the standard library. Since we have a generic we do not need to create a macro rule for this. 

```rust
impl<T: Number> From<T> for Vec2<T> {
    fn from(val: T) -> Self {
        Vec2 {
            x: val,
            y: val,
        }
    }
}
```

## Lets quickly write a test

We first need to implement partial equals for the Vec2 struct. This is simple. We just need to add it to number and then derive it in Vec2.

```rust
pub trait Number: 
    PartialEq +
    ...
```

```rust
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Vec2<T: Number>  {
    ...
```

> Note: I removed the `main.rs` file from the previous article

We can then write our test in `lib.rs` as follows:

```rust
pub mod vec2;

#[cfg(test)]
mod tests {
    use crate::vec2::Vec2;

    #[test]
    fn it_works() {
        // 5, 5
        let v1: Vec2<i32> = 5.into();

        // 1, 2
        let v2 = Vec2 {
            x: 1,
            y: 2
        };

        // 6, 7
        let mut v3 = v1 + v2;

        // 12, 14
        v3 *= Vec2::from(2);

        assert_eq!(v3, Vec2 {
            x: 12,
            y: 14
        });
    }
}
```

We can now run the test with:

```bash
cargo test
```

And we get the following output.

![The terminal output for the test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6p1brkhk82pw033tqhk0.png)

IT WORKS!!!

Find the full code here: [Trait Implementation Wizardry](https://github.com/cleggacus/rust-traits-example/tree/2_trait_implementation_wizardry_macros)

## Challenge
Try and write some macros for casting between numbers using the from trait.

## Conclusion
We have explored how to fix the issue with repeating trait implementation blocks by using macro rules and how we can practically do this with the Vec2 example from the previous article. If you have any questions leave a comment and it would be much appreciated if you drop a like if you enjoyed this article, thanks!