I am working on a Rust macro that needs to accept the file name as a argument. Based on the file name I want to parse and do some operations and generate some code. I believe proc_macro in Rust should help me achieve my requirements. However, I am facing issues passing file!() as argument to the procedural macro. The idea is to get the file name via the file!() macro and pass it as argument to my procedural macro. This is my current implementation:
// lib.rs - proc_macro crate
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr);
// perform operation on input string
let expanded = quote! {
#output_str
};
TokenStream::from(expanded)
}
file!() is an expression that expands to a string literal. Your proc_macro can parse it as an expression and then evaluate it.
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as Expr);
let expanded = quote! {{
let file = #input;
println!("{file}");
file
}};
TokenStream::from(expanded)
}
More context: Procedural macros receive a sequence of tokens (token stream). The compiler does not resolve nested macros the same way it calls nested functions. Macro resolution is "inverted" in this regard. Macro invocations are resolved recursively; the file!() expression is produced in its output token stream, and the compiler resolves it later.
Your proc_macro can parse it as an expression and then evaluate it.
I disagree with this wording though, the aim is to paste file!() into the output verbatim, not to expand it and paste the result, as the word "evaluate", to me, would imply.
That's very fair! The compiler still does the evaluation. It just occurs outside of your macro. I find it interesting that proc macros are just token processors. That's the real takeaway, here.
Thank @parasyte ! This does help me achieve my goal for the macro. Much appreciated!
One follow-up question to understand better, in Rust, if we have expressions that are compile time constants, such as this parsing of base directory from file!(), should we use these proc_macros approach or use constant functions? As I understand with my limited knowledge in Rust, constant functions gets evaluated compile time and help reduce the runtime overhead.
Macros and const eval can both achieve similar levels of reduction to runtime overhead. Const eval is still a work in progress and may not have all of the necessary machinery stabilized. Const eval is potentially better for ergonomics as long as your use case is part of the language subset that is eligible for constant evaluation.
It might be obvious, but I should also point out that const fn is not a guarantee that the function runs only at compile time. The modifier relaxes constraints in the type system to enable evaluation in constant contexts[1]. E.g., assigning the result of a const fn call to a const value. See: When are Rust's const fns executed? Otherwise, it's just a normal function call at runtime.
Also note that I'm not super familiar with const eval in Rust, having used it only sparingly. I don't really know the full scope of what's currently supported or the maximal subset that can be evaluated at compile-time. Procedural macros don't have the same limitations; macros can run any Rust code at compile-time (including I/O)! It's an unusual way to do metaprogramming and it doesn't have any type system information (no reflection), but it's quite capable regardless.
This relaxation is a result of constraining the types that the function is allowed to accept and return, and the body can only contain constant expressions. âŠī¸