Latest Post

Useful Rust Macros

In the course of working on various projects, I find myself doing similar patterns in rust, especially when modeling a domain. I want to document some of the common patterns that I find.

Error types

When you start down the path of specific error types of everything in your system, you end up with alot of structs and enums that create and combine together to talk about the error space of any given function.

I often find myself defining structs for specific errors, and then combining them together for each function call, so I created a couple of macros to make that more streamlined.

#[macro_export]
macro_rules! domain_error {
    ($error:ident, $msg:expr) => {
        paste::paste! {
        #[derive(Debug, thiserror::Error)]
        #[error($msg)]
        pub struct [< $error Error >];
        }
    };
}

This first macro creates a struct that is a specific error with a message associated with that error.

#[macro_export]
macro_rules! group_error {
    ($base:ident, [ $($error:tt),+ ]) => {
        paste::paste! {
            #[derive(Debug, thiserror::Error)]
            pub enum $base {
                $(
             #[error("{0}")]
                $error([< $error Error >])
             ),+
            }

            $(
                impl From<[< $error Error >]> for $base {
                fn from(value: [< $error Error >]) -> Self {
                    Self::$error(value)
                }
            }
            )+

        }
    };
}

This second macro groups the specific error into an enum for when I need to return a couple of different errors from a function. Ideally, the base level functions always return a specific, concrete error, and then the higher level functions combine possible errors together.

New types

When modeling a domain, you often want to wrap primative types in a new type. This is tedious, but very useful for type correctness. No longer will you be able to pass in an id in place of an email, etc.

#[macro_export]
macro_rules! NewTypeImpl {
    ($name:ident, $base:ty) => {
        impl std::ops::Deref for $name {
            type Target = $base;

            fn deref(&self) -> &Self::Target {
                &self.0
            }
        }
    };
}

#[macro_export]
macro_rules! NewType {
        ($name:ident, $base:ty, [ $($derives:ident),+ ]) => {
                        #[derive($($derives),+)]
                        pub struct $name($base);
                        framework::NewTypeImpl!($name, $base);
                };
    ($name:ident, $base:ty) => {
        pub struct $name($base);
        framework::NewTypeImpl!($name, $base);
    };
}

These macros combine together to define a wrapper NewType that can be dereferenced to the primative type that it contains, with option derives.

Last two posts