Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Procedural macros

Source

Initialize a new workspace with cargo init --lib and add tokio to the dependencies via cargo add tokio --features full.

#![allow(unused)]
fn main() {
#[tokio::main]
async fn proc_macro_main() {}
}

Procedural macros can manipulate the syntax tree directly. They work with TokenStreams which are sequences of tokens representing Rust code. A proc macro receives a TokenStream as input and returns a TokenStream as output.

Since proc macros can be expanded to arbitrary Rust code based on the implementation of the macro, we will focus on the expanded Rust code rather than the generated binary files in this chapter.

If we look at the #[tokio::main] attribute proc macro as an example, we see that it is expanded to this or similar code:

fn main() {
    tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .unhandled_panic(UnhandledPanic::ShutdownRuntime)
        .build()
        .unwrap()
        .block_on(async {
            let _ = tokio::spawn(async {
                panic!("This panic will shutdown the runtime.");
            }).await;
        })
}

proc_macro_main

#![allow(unused)]
fn main() {
#[tokio::main]
async fn proc_macro_main() {}
}
$ cargo expand
...
fn proc_macro_main() {
    let body = async {};
    #[allow(
        clippy::expect_used,
        clippy::diverging_sub_expression,
        clippy::needless_return
    )]
    {
        return tokio::runtime::Builder::new_multi_thread()
            .enable_all()
            .build()
            .expect("Failed building the Runtime")
            .block_on(body);
    }
}