Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Rust Web Programming

You're reading from   Rust Web Programming A hands-on guide to developing fast and secure web apps with the Rust programming language

Arrow left icon
Product type Paperback
Published in Feb 2021
Publisher Packt
ISBN-13 9781800560819
Length 394 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Maxwell Flitton Maxwell Flitton
Author Profile Icon Maxwell Flitton
Maxwell Flitton
Arrow right icon
View More author details
Toc

Table of Contents (19) Chapters Close

Preface 1. Section 1:Setting Up the Web App Structure
2. Chapter 1: Quick Introduction to Rust FREE CHAPTER 3. Chapter 2: Designing Your Web Application in Rust 4. Section 2:Processing Data and Managing Displays
5. Chapter 3: Handling HTTP Requests 6. Chapter 4: Processing HTTP Requests 7. Chapter 5: Displaying Content in the Browser 8. Section 3:Data Persistence
9. Chapter 6: Data Persistence with PostgreSQL 10. Chapter 7: Managing User Sessions 11. Chapter 8: Building RESTful Services 12. Section 4:Testing and Deployment
13. Chapter 9: Testing Our Application Endpoints and Components 14. Chapter 10: Deploying Our Application on AWS 15. Chapter 11: Understanding Rocket Web Framework 16. Assessments 17. Other Books You May Enjoy Appendix A: Understanding the Warp Framework

Metaprogramming with macros

Metaprogramming can generally be described as a way in which the program can manipulate itself based on certain instructions. Considering the strong typing Rust has, one of the simplest ways in which we can meta program is by using generics. A classic example of demonstrating generics is through coordinates:

struct Coordinate <T> {
    x: T, 
    y: T
}
fn main() {
     let one = Coordinate{x: 50, y: 50};
     let two = Coordinate{x: 500, y: 500};
     let three = Coordinate{x: 5.6, y: 5.6};
}

Here, the compiler is looking for all the times where the coordinate struct is called and creates structs with the types that were used when compiling. The main mechanism of metaprogramming in Rust is done with macros. Macros enable us to abstract code. We've already been using macros in our print functions. The ! notation at the end of the function denotes that this is a macro that's being called. Defining our own macros is a blend of defining a function and using a lifetime notation within a match statement in the function. In order to demonstrate this, we will define a macro that capitalizes a string:

macro_rules! capitalize {
    ($a: expr) => {
        let mut v: Vec<char> = $a.chars().collect();
        v[0] = v[0].to_uppercase().nth(0).unwrap();
        $a = v.into_iter().collect();
    }
}
fn main() {
    let mut x = String::from("test");
    capitalize!(x);
    println!("{}", x);
}

Instead of using the term fn, we use the macro_rules! definition. We then say that $a is the expression that's passed into the macro. We get the expression, convert it into a vector of chars, uppercase the first char, and then convert it back into a string.

Note that we don't return anything in the capitalize macro and that when we call the macro, we don't assign a variable to it. However, when we print the x variable at the end, we can see that it is capitalized. This does not behave like an ordinary function. We also have to note that we didn't define a type. Instead, we just said it was an expression; the macro still does checks via traits. Passing an integer into the macro results in the following error:

|     capitalize!(32);
|     ---------------- in this macro invocation
|
= help: the trait `std::iter::FromIterator<char>` is not implemented for `{integer}`

Lifetimes, blocks, literals, paths, meta, and more can also be passed instead of an expression. While it's important to have a brief understanding of what's under the hood of a basic macro for debugging and further reading, diving more into developing complex macros will not help us when it comes to developing web apps.

We must remember that macros are a last resort and should be used sparingly. Errors that are thrown in macros can be hard to debug. In web development, a lot of the macros are already defined in third-party packages. Because of this, we do not need to write macros ourselves to get a web app up and running. Instead, we will mainly be using derive macros out of the box.

Derive macros can be analogous to decorators in JavaScript and Python. They sit on top of a function or struct and change its functionality. A good way to demonstrate this in action is by revisiting our coordinate struct. Here, we will put it through a print function we define, and then try and print it again with the built-in print macro:

struct Coordinate {
    x: i8, 
    y: i8
}
fn print(point: Coordinate) {
    println!("{} {}", point.x, point.y);
}
fn main() {
    let test = Coordinate{x: 1, y:2};
    print(test);
    println!("{}", test.x)
}

Unsurprisingly, we get the following error when compiling:

|     let test = Coordinate{x: 1, y:2};
|         ---- move occurs because `test` has type           `Coordinate`, which does not implement the `Copy`           trait
|     print(test);
|           ---- value moved here
|     println!("{}", test.x)
|                         ^^^^^^ value borrowed here after move

Here, we can see that we're getting the error that the coordinate was moved into our function and was then borrowed later. We can solve this with the & notation. However, it's also worth noting the second line in the error, stating that our struct does not have a copy trait. Instead of trying to build a copy trait ourselves, we can use a derive macro to give our struct a copy trait:

#[derive(Clone, Copy)]
struct Coordinate {
    x: i8, 
    y: i8
}

Now, the code will run. The copy trait is fired when we move the coordinate into our print function. We can stack these traits. By merely adding the debug trait to the derive macro, we can print out the whole struct using the :? operator in the print macro:

#[derive(Debug, Clone, Copy)]
struct Coordinate {
    x: i8, 
    y: i8
}
fn main() {
    let test = Coordinate{x: 1, y:2};
    println!("{:?}", test)
}

This gives us a lot of powerful functionality in web development. For instance, we will be using them in JSON serialization using the serde crate:

use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Coordinate {
    x: i8, 
    y: i8
}

With this, we can pass the coordinate into the crate's functions to serialize into JSON, and then deserialize. We can create our own derive macros, but the code behind our own derive macros has to be packaged in its own crate. While we will go over cargo and file structure in the next chapter, we will not be building our own derive macros.

You have been reading a chapter from
Rust Web Programming
Published in: Feb 2021
Publisher: Packt
ISBN-13: 9781800560819
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime