简体   繁体   中英

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);
}

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:

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM