Why are the existing guides to procedural macros so scattered?

I've been trying to write a procedural macro for a basic quick slap it on and time a function decorator. Yes, I know about criterion, statistical tests and compiling with max optimization. The point isn't the timing, it's that procedural macros have sent me far and wide across the rust book and broader bloggosphere looking for resources for hours, and I still can't figure out how to write a silly little procmacro.

Why aren't procmacros better documented in the rust book? The existing single page in the rust book seems almost an insult to their complexity, without any reference where to look for more in depth content. Why do I have to wander across the LogRocket blog, the syn docs, and the reference, only to feel like I'm missing some crucial pieces of how to write procmacros?

PS. I still haven't gotten my procmacro to work. If you're a wizard, feel free to point me at resources or even fix my macro.

/*
#[timings]
fn f(){
...blah...
}
// should generate:
fn f(){
    let now = std::time::Instant::now();
    ...blah...
    println!("time:{:?}", now.elapsed());
}
*/
// attempt:
#[proc_macro_attribute]
pub fn timings(attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(item as syn::ItemFn);
    let name = &input.ident;
    let abi = &input.abi;
    /*
      let gen = quote! {
        fn stringify!(#name, _timing)(){
          let now = std::time::Instant::now();
          /* fn body */
          println!("time:{:?}", now.elapsed());
        }
      };

    gen.into()*/
    todo!();
}

a more comprehensive list of the linkscattering of procmacro knowledge, found here:

IN particular, I think my frustration with my proc macro situation has to do with nearly all existing docs that I can find having to do with derive macros, while I can't seem to find much documentation of how to implement either function type or attribute type macros.

I think I want something like this:

let ast: syn::Item = syn::parse_macro_input!(item);

But I'm running into "cannot find item in syn" errors, even with the full feature gate unlocked
syn = {version = "1.0.76", full = true}

Hi there,
I‘m fully with you wrt. the hard to find proc-macro documentation or guide where and how to start.

However, I can’t provide any better links but if you don’t mind you could look at one of my crates:
Little ProcMacro

This uses a function decoration and after some validation which you might want to skip it just recreates the function with a different name and specific linker attributes containing the original function body. I hope this may help with your current issue :wink:

The knowledge on how to build this was mainly found in existing code bases and adjusted to my needs :blush:

I believe derived macros were stabilised before the other types of proc macro, so that may explain why many things (particularly older documents) focus on derive macros. However, looking at the description in the book page on macros, it seems attribute and function macros work very similarly, with a different function signature and attribute annotating the function, so I guess a longer guide to them would perhaps overlap a lot with the stuff on derive macros.

Unless I'm missing something, that doesn't look like the right way to enable features. Based on the features section of the cargo book, it seems like you need:

syn = {version = "1.0.76", features = ["full"]}
1 Like

This actually got me to a functioning proc-macro. Nice!

That not withstanding, I'm still pretty disappointed by the state of proc-macro documentation. Thanks for the help.

#[proc_macro_attribute]
pub fn timings(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let ast = syn::parse_macro_input!(item as syn::ItemFn);
    handle_timings_macro(&ast)
}

fn handle_timings_macro(ast: &syn::ItemFn) -> TokenStream {
    let vis = &ast.vis;
    let sig = &ast.sig;
    let block_stmts = &ast.block.stmts;
    let gen = quote::quote! {
      #vis #sig
      {
        let now = std::time::Instant::now();
        #(#block_stmts)*
        println!("time: {:?}", now.elapsed());
      }
    };
    gen.into()
}

The complexity and difficulty of procedural macros does not lie in how to write them. That's simple, almost schematical: parse the input, likely using syn, interpret it, emit output using quote.

The difficulty lies in what to write, that is, what you want your proc-macro to do. Alas, that is not something a guide can be written about. It will be specific to every new use case and proc-macro you want to write, unless your are lucky enough for your problem to fall into the convenient category of "just invoke this one method on every field".

Totally agree that the power of macros is large, complex, and difficult to put into any single book, much less a blog post or page from the Reference or equiv.

I think we disagree about how difficult it is to get up and running writing procedural macros. Syntax is simple, getting up and running isn't: merely the lack of existing examples through the standard resources makes getting started quite daunting.

That I couldn't find a non-trivial example of a function or attribute macro in the standard references, and had to wade through out of date material about them, ultimately resorting to copy the syntax provided by a helpful stranger on a forum, after having looked for hours for resources, signals to me that this is an area in need of improvement in the rust documentation.

I'm curious as to what you think is lacking in the Book's chapter on proc-macros. They demonstrate exactly what you are looking for: the basics of getting started with all three types of proc-macros. There's not much more one can hope for in a generic introduction.

Specifically what do you think a "non-trivial" example should contain? There isn't much non-trivial around getting a proc-macro skeleton to compile and run.

The hard part in, for instance, a derive macro is being able to abstract over the trait you are deriving. That is, having extensive understanding of its fully general usage, and knowing the hopefully few, but inevitable, domain-specific ways of using it incorrectly (which constitutes code that the derive macro should never emit).

Since any author of a general proc-macro guide will not, cannot, have an understanding of whatever abstraction you will come up with, they can't explain to you domain-specific pitfalls in detail.

What you can do is study the source of commonly used, high-quality proc-macros. For instance, look for the GitHub profile of David Tolnay, the author of Serde among many others. However, solutions to complex
problems are usually not so simple as to be a good fit for an introductory guide.

When I needed procedural macro similar to what topicstarter needed my first impulse was to think about where I can find a good example of such macro.
And after decided that tokio::main does more-or-less what I need I spend 5min looking in Google for the sources.
Found them here and in half-hour solved my problem.
If that, petty obvious, solution doesn't work for you for some reason then I'm not sure what's the issue, ultimately.
Maybe it's just difference in cultures, IDK. Because I always look for the source of something-similar-to-what-I-need first and only try to reach out for the tutorials or forums when source is unreadable.
This rarely happens with Rust and for every piece where I needed that (like async/await) there were pretty good tutorials.
procmacro wasn't such an issue, but maybe you can explain what nontrivial part is not easily googleable?

1 Like

Have you seen dtolnay's proc macro workshop materials?

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.