Can a Rust macro create new identifiers?
Asked Answered
A

4

20

I'd like to create a setter/getter pair of functions where the names are automatically generated based on a shared component, but I couldn't find any example of macro rules generating a new name.

Is there a way to generate code like fn get_$iden() and SomeEnum::XX_GET_$enum_iden?

Armor answered 11/12, 2014 at 3:44 Comment(1)
is there a 2021 answer :D?Dunghill
M
21

If you are using Rust >= 1.31.0 I would recommend using my paste crate which provides a stable way to create concatenated identifiers in a macro.

macro_rules! make_a_struct_and_getters {
    ($name:ident { $($field:ident),* }) => {
        // Define the struct. This expands to:
        //
        //     pub struct S {
        //         a: String,
        //         b: String,
        //         c: String,
        //     }
        pub struct $name {
            $(
                $field: String,
            )*
        }

        paste::paste! {
            // An impl block with getters. Stuff in [<...>] is concatenated
            // together as one identifier. This expands to:
            //
            //     impl S {
            //         pub fn get_a(&self) -> &str { &self.a }
            //         pub fn get_b(&self) -> &str { &self.b }
            //         pub fn get_c(&self) -> &str { &self.c }
            //     }
            impl $name {
                $(
                    pub fn [<get_ $field>](&self) -> &str {
                        &self.$field
                    }
                )*
            }
        }
    };
}

make_a_struct_and_getters!(S { a, b, c });
Meistersinger answered 29/4, 2020 at 20:59 Comment(0)
M
15

My mashup crate provides a stable way to create new identifiers that works with any Rust version >= 1.15.0.


#[macro_use]
extern crate mashup;

macro_rules! make_a_struct_and_getters {
    ($name:ident { $($field:ident),* }) => {
        // Define the struct. This expands to:
        //
        //     pub struct S {
        //         a: String,
        //         b: String,
        //         c: String,
        //     }
        pub struct $name {
            $(
                $field: String,
            )*
        }

        // Use mashup to define a substitution macro `m!` that replaces every
        // occurrence of the tokens `"get" $field` in its input with the
        // concatenated identifier `get_ $field`.
        mashup! {
            $(
                m["get" $field] = get_ $field;
            )*
        }

        // Invoke the substitution macro to build an impl block with getters.
        // This expands to:
        //
        //     impl S {
        //         pub fn get_a(&self) -> &str { &self.a }
        //         pub fn get_b(&self) -> &str { &self.b }
        //         pub fn get_c(&self) -> &str { &self.c }
        //     }
        m! {
            impl $name {
                $(
                    pub fn "get" $field(&self) -> &str {
                        &self.$field
                    }
                )*
            }
        }
    }
}

make_a_struct_and_getters!(S { a, b, c });
Meistersinger answered 28/4, 2018 at 18:43 Comment(0)
S
9

No, not as of Rust 1.22.


If you can use nightly builds...

Yes: concat_idents!(get_, $iden) and such will allow you to create a new identifier.

But no: the parser doesn't allow macro calls everywhere, so many of the places you might have sought to do this won't work. In such cases, you are sadly on your own. fn concat_idents!(get_, $iden)(…) { … }, for example, won't work.

Supremacist answered 11/12, 2014 at 4:46 Comment(0)
S
0

There's a little known crate gensym that can generate unique UUID names and pass them as the first argument to a macro, followed by a comma:

macro_rules! gen_fn {
    ($a:ty, $b:ty) => {
        gensym::gensym!{ _gen_fn!{ $a, $b } }
    };
}

macro_rules! _gen_fn {
    ($gensym:ident, $a:ty, $b:ty) => {
        fn $gensym(a: $a, b: $b) {
            unimplemented!()
        }
    };
}

mod test {
    gen_fn!{ u64, u64 }
    gen_fn!{ u64, u64 }
}

If all you need is a unique name, and you don't care what it is, that can be useful. I used it to solve a problem where each invocation of a macro needed to create a unique static to hold a singleton struct. I couldn't use paste, since I didn't have unique identifiers I could paste together in the first place.

Scup answered 16/7, 2021 at 13:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.