loading...

Practical Rust Web Development - Macros

werner profile image Werner Echezuría ・2 min read

Practical Rust Web Development (14 Part Series)

1) Practical Rust Web Development - API Rest 2) Practical Rust Web Development - Connection Pool 3 ... 12 3) Practical Rust Web Development - Authentication 4) Practical Rust Web Development - Testing 5) Practical Rust Web Development - Cors 6) Practical Rust Web Development - Front-End 7) Practical Rust Web Development - Searching 8) Practical Rust Web Development - Pagination 9) Practical Rust Web Development - Associations 10) Practical Rust Web Development - Macros 11) Practical Rust Web Development - CI Travis 12) Practical Rust Web Development - GraphQL 13) Practical Rust Web Development - State Machine 14) Migrate to Actix-Web 2.0

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.

Practical Rust Web Development (14 Part Series)

1) Practical Rust Web Development - API Rest 2) Practical Rust Web Development - Connection Pool 3 ... 12 3) Practical Rust Web Development - Authentication 4) Practical Rust Web Development - Testing 5) Practical Rust Web Development - Cors 6) Practical Rust Web Development - Front-End 7) Practical Rust Web Development - Searching 8) Practical Rust Web Development - Pagination 9) Practical Rust Web Development - Associations 10) Practical Rust Web Development - Macros 11) Practical Rust Web Development - CI Travis 12) Practical Rust Web Development - GraphQL 13) Practical Rust Web Development - State Machine 14) Migrate to Actix-Web 2.0

Posted on by:

werner profile

Werner Echezuría

@werner

Ruby on Rails developer and Rust enthusiast.

Discussion

markdown guide
 

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.

 

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...

 

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!