DEV Community

Werner Echezuría
Werner Echezuría

Posted on

Practical Rust Web Development - Macros

One thing someone might notice is how boilerplate we need to write comparing to other programming languages, specially for the web, however, there is some code we need to write, like types, that many of us agree are necessary to improve the development process.

We can reduce code in other areas and we have macros that allows us to do that, however we need to be careful, because macros can make our code hard to maintain. So, use macros sparingly.

Let's create a macro to reduce the amount of code we write for a handler. At first it might seem overwhelming, but, once you understand them, they can be a powerful tool.

src/handlers/function_handler.rs:


macro_rules! function_handler {
    ( $handler_name:ident ($($arg:ident:$typ:ty),*) -> $body:expr) => {
        pub fn $handler_name(user: LoggedUser, pool: web::Data<PgPool>, $($arg:$typ,)*) 
            -> impl Future<Item = HttpResponse, Error = actix_web::Error>
        {
            web::block(move || {
                let pg_pool = pool
                    .get()
                    .map_err(|_| {
                        crate::errors::MyStoreError::PGConnectionError
                    })?;
                $body(user, pg_pool)
            })
            .then(|res| match res {
                Ok(data) => Ok(HttpResponse::Ok().json(data)),
                Err(_) => Ok(HttpResponse::InternalServerError().into()),
            })
        }
    };
}

We declare a macro with macro_rules!, you could even create new syntax with a macro, in our case we want to create handlers following the next syntax: handler name (params) -> block of code, that can be expressed in this way: $handler_name:ident ($($arg:ident:$typ:ty),*) -> $body:expr, $handler_name is an identifier, the name of our handler, then we pass the parameters, because we don't know how many of them are, we are separating them with a comma and use this syntax: $(),*, this is to tell Rust we have infinity amount of parameters. Now we need a way to tell Rust to split parameters from block of code, I'm using an arrow to achieve that.

We identify our closure as $body and then pass the parameters so we can use it in our handlers, specifically the logged user and the database connection.

Now compare this:

pub fn index(user: LoggedUser, pool: web::Data<PgPool>) 
    -> impl Future<Item = HttpResponse, Error = actix_web::Error> {
        web::block(move || {
            let pg_pool = pool
                .get()
                .map_err(|_| {
                    crate::errors::MyStoreError::PGConnectionError
                })?;
            PriceList::list(user.id, &pg_pool)
        })
        .then(|res| match res {
            Ok(data) => Ok(HttpResponse::Ok().json(data)),
            Err(error) => Err(actix_web::error::ErrorInternalServerError(error)),
        })
}

with this:

function_handler!(
    index () -> (|user: LoggedUser, pg_pool: PgPooledConnection| {
        PriceList::list(user.id, &pg_pool)
    })
);

Pretty cool, right?

Source code here.

Top comments (3)

Collapse
 
deciduously profile image
Ben Lovy

Great use of macros, I hadn't thought to try it for web handlers! I think macro_rules! has been one of my favorite things to learn about in Rust.

Collapse
 
werner profile image
Werner Echezuría

Yeah!,me too, although I had to remind me to be very careful because it could become some spaghetti monster hard to maintain, like github.com/bitex-la/bitprim-rust/b...

Collapse
 
deciduously profile image
Ben Lovy

Oof, that looks hellish to debug - and the error messages are not helping you out much! I've started dabbling with nested macros and already it feels little too stringy for my tastes - so useful though!