Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create a function-like procedural macro?

How should a_proc_macro be defined so it "returns" a 5?

fn main() {
    let a = a_proc_macro!();
    assert!(a == 5);
}
like image 780
Shepmaster Avatar asked Nov 18 '19 20:11

Shepmaster


People also ask

What is a procedural macro?

Procedural macros allow you to run code at compile time that operates over Rust syntax, both consuming and producing Rust syntax. You can sort of think of procedural macros as functions from an AST to another AST. Procedural macros must be defined in a crate with the crate type of proc-macro .

Why use macros instead of functions Rust?

Rust has excellent support for macros. Macros enable you to write code that writes other code, which is known as metaprogramming. Macros provide functionality similar to functions but without the runtime cost. There is some compile-time cost, however, since macros are expanded during compile time.


2 Answers

Reading The Rust Programming Language's chapter on macros says:

Function-like macros define macros that look like function calls. Similarly to macro_rules! macros, they’re more flexible than functions; for example, they can take an unknown number of arguments. However, macro_rules! macros can be defined only using the match-like syntax we discussed in the section “Declarative Macros with macro_rules! for General Metaprogramming” earlier. Function-like macros take a TokenStream parameter and their definition manipulates that TokenStream using Rust code as the other two types of procedural macros do. An example of a function-like macro is an sql! macro that might be called like so:

let sql = sql!(SELECT * FROM posts WHERE id=1);

This macro would parse the SQL statement inside it and check that it’s syntactically correct, which is much more complex processing than a macro_rules! macro can do. The sql! macro would be defined like this:

#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {

This definition is similar to the custom derive macro’s signature: we receive the tokens that are inside the parentheses and return the code we wanted to generate.


Since Rust 1.45, you can invoke function-like procedural macros as an expression.

example
├── Cargo.toml
├── example-macro
│   ├── Cargo.toml
│   ├── src
│   │   └── lib.rs
├── src
│   └── main.rs

Cargo.toml

[package]
name = "example"
version = "0.1.0"
edition = "2018"

[dependencies]
example-macro = { path = "example-macro" }

src/main.rs

fn main() {
    assert_eq!(example_macro::a_proc_macro!(), 5);
}

example-macro/Cargo.toml

[package]
name = "example-macro"
version = "0.1.0"
edition = "2018"

[lib]
proc-macro = true

example-macro/src/lib.rs

extern crate proc_macro;

use proc_macro::TokenStream;

#[proc_macro]
pub fn a_proc_macro(_input: TokenStream) -> TokenStream {
    "5".parse().unwrap()
}

See also:

  • How do I create a proc_macro_attribute?
  • Convert string into TokenStream
  • Why do proc-macros have to be defined in proc-macro crate?
  • Tracking issue for procedural macros and "hygiene 2.0" (#54727)
like image 188
Shepmaster Avatar answered Nov 15 '22 11:11

Shepmaster


Directly defining expression-like procedural macros is not yet possible in stable Rust. If you can use nightly, Shepmaster's answer shows how.

If you are on stable, you can still emulate expression-like procedural macros as follows:

  • define a procedural macro that expands to a function that evaluates to the expression you want to call;
  • then define a regular macro that expands to a block that embeds the function definition and call.

In your case you'd define the procedural macro like this:

#[proc_macro]
pub fn a_proc_macro_impl(_input: TokenStream) -> TokenStream {
    "fn output() -> usize { 5 }".parse().unwrap()
}

...the helper macro_rules! macro follows this pattern:

macro_rules! a_proc_macro {
    ($($t:tt)*) => {{
        struct _X;
        impl _X {
            a_proc_macro!($($t)*);
        }
        _X::output()
    }}
}

This is a hack, and a cumbersome one at that, but proc-macro-hack crate comes to the rescue and makes it easier to generate procedural macros using the above technique. With the help of the proc-macro-hack crate, you can run the almost unchanged code from Shepmaster's answer on stable:

  • edit both Cargo.toml files and add proc-macro-hack = "0.5.11" to the dependencies section;
  • add #[proc_macro_hack] use example_macro::a_proc_macro; in src/main.rs, and invoke a_proc_macro! from the local namespace.
  • add #[proc_macro_hack::proc_macro_hack] before the definition of a_proc_macro in example-macro/src/lib.rs.
like image 40
user4815162342 Avatar answered Nov 15 '22 09:11

user4815162342