How to declare a variable in declare macro?

#[macro_export]
macro_rules! crud {
    ($( $name:expr ),* ) => {
        $(
            let name_uppercase: String = stringify!($name).split('_').map(|x| x[0..1].to_uppercase() + &x[1..]).collect();
            paste! {
                #[debug_handler]
                pub async fn [<create_ $name>](
                    c: JwtClaims,
                    Json(payload): Json<[<Create name_uppercase Info>]>,
                ) -> AppResult<Json<CommonResponse<db::[<$name>]::Data>>> {
                    let client = DB.get().unwrap();
                    CommonResponse::json_data(
                        client
                            .[<$name>]()
                            .create(...)
                            .exec()
                            .await?,
                    )
                }
            }
        )*
    };
}

I have some code like this, and what I want to do is generating functions for each $name, but it reports that I can't use let ... in the expanding macro.

I assume you are calling this macro at module-level, which does not allow arbitrary let-bindings. The paste crate supports case conversion, have you tried that? I.e. could you try if this creates the output you desire:

#[macro_export]
macro_rules! crud {
    ($( $name:expr ),* ) => {
        $(
            paste! {
                #[debug_handler]
                pub async fn [<create_ $name>](
                    c: JwtClaims,
                    Json(payload): Json<[<Create $name:camel Info>]>,
                ) -> AppResult<Json<CommonResponse<db::[<$name>]::Data>>> {
                    let client = DB.get().unwrap();
                    CommonResponse::json_data(
                        client
                            .[<$name>]()
                            .create(...)
                            .exec()
                            .await?,
                    )
                }
            }
        )*
    };
}
1 Like

Oh ya it works! But still I need some sort of manipulation on the $name, maybe I will cut the name, remain parts of it, so I wonder if there is anyway to do it? Or how did paste work?

Paste is actually a function-like procedural macro under the hood, not a declarative macro. Procedural macros offer you a more flexible API and allow you to do more complex token stream manipulations than declarative macros. You could write a procedural macro yourself with the name manipulation you desire.

Okay thanks, one last question, if there's a declare macro in a function-like procedural macro, is it checking the declare macro rules first then procedural macro rules?

AFAIK the compiler does not treat procedural macros differently from declarative macros when it comes to expansion order. Expansion happens in passes, see here:

There is one further thing to note about expansion: what happens when a syntax extension expands to something that contains another syntax extension invocation. For example, consider an alternative definition of four!; what happens if it expands to 1 + three!()?

let x = four!();

Expands to:

let x = 1 + three!();

This is resolved by the compiler checking the result of expansions for additional syntax extension invocations, and expanding them. Thus, a second expansion step turns the above into:

let x = 1 + 3;

The takeaway here is that expansion happens in "passes"; as many as is needed to completely expand all invocations.

So your outer macro is expanded in the first pass. If the compiler discovers a second macro call inside your macro invocation (regardless whether it is a procedural or a declarative macro), it expands the second macro call in the second pass and so on.

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.