MaxV
MaxV

Reputation: 2810

Macros with three optional parameters and one of them have to accept list of values

I have a structure with two optional fields and would like to provide special macro to simplify instances creation.

The macro have to accept one required argument and three optional arguments. Last optional argument have to accept list of values. And I would like to use exactly one patter for that.

Finally I did that and everything works fine:

#[derive(Debug)]
pub enum Variant {
    OptionOne,
    OptionTwo,
}

#[derive(Debug)]
pub struct TheStruct{
    string: String,
    first: Option<Variant>,
    second: Option<Variant>,
    numbers: Vec<u32>,
}

impl TheStruct {
    // doesn't matter
}

#[doc(hidden)]
#[macro_export]
macro_rules! the_macro_int {
    ($the_struct:ident, numbers, { $($w:expr),*$(,)*}) => {
        $($the_struct.numbers.push($w);)*
    };
    ($the_struct:ident, first, {$w:expr}) => {
        $the_struct.first = Some($w);
    };
    ($the_struct:ident, second, {$w:expr}) => {
        $the_struct.second = Some($w);
    };
}

#[macro_export]
macro_rules! the_macro {
    (string: $string:expr, $($kind:ident : $val:tt),*$(,)*) => {{
        let mut the_struct = $crate::TheStruct{
            string: $string,
            first: None,
            second: None,
            numbers: std::vec::Vec::new(),
        };
        $($crate::the_macro_int!(the_struct, $kind, $val);)*
        the_struct
    }};
}

fn main() {
    let the_struct = the_macro!(
        string: "Hello".to_owned(),
        first: { Variant::OptionOne },
        numbers: (1, 3, 4),
    );
    println!("the_struct:{:?}", the_struct);
}

Playground

The one thing makes me unhappy: the brackets in first: { Variant::OptionOne },.

I tried to replace my pattern by (string: $string:expr, $($kind:ident : $val:expr),*$(,)*) but it doesn't work for numebrs anymore.

Is it possible to redefine pattern for the_macro to allow first: Variant::OptionOne, to be a valid while numbers still can accept a list of items? Kind of brackets for numbers doesn't matter but I can't replace list of things with just Vec or something similar.

P.S. I don't looking for a solution with multiple patterns.

Upvotes: 1

Views: 828

Answers (1)

mcarton
mcarton

Reputation: 30061

There are two problems with your macro:

  • First it tries to do too much. Why initialize numbers with numbers: (1, 3, 4) while array literals use [/] and you can initialize a vector with only 4 extra characters?

    Let's use the more natural notation numbers: vec![1, 3, 4]. This has the advantage of letting your user initialize the field with an existing vector too.

  • Second, you need to use brackets around { Variant::OptionOne } because you have used tt for your value, but Variant::OptionOne isn't one tt, it's 3. If you change the pattern to a more natural expr, you can use Variant::OptionOne directly:

    (string: $string:expr, $($kind:ident : $val:expr),*$(,)*) => {{
    

(Permalink to the playground)


I would however advise you not to use such a macro. The usual way to deal with initialization of such structs in Rust is the builder pattern.

Upvotes: 1

Related Questions