How should a_proc_macro
be defined so it "returns" a 5?
fn main() {
let a = a_proc_macro!();
assert!(a == 5);
}
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 .
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.
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 withmacro_rules!
for General Metaprogramming” earlier. Function-like macros take aTokenStream
parameter and their definition manipulates thatTokenStream
using Rust code as the other two types of procedural macros do. An example of a function-like macro is ansql!
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. Thesql!
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:
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:
Cargo.toml
files and add proc-macro-hack = "0.5.11"
to the dependencies section;#[proc_macro_hack] use example_macro::a_proc_macro;
in src/main.rs
, and invoke a_proc_macro!
from the local namespace.#[proc_macro_hack::proc_macro_hack]
before the definition of a_proc_macro
in example-macro/src/lib.rs
.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With