Golang like array initialized with keys macro. Feedback would be appreciated

The code below does not work

macro_rules! vec_with_keys {
    ($x:literal $sep:tt $y:literal $($tail:tt)*) => {
        {
            println!("Value pair received: ({}, {})", $x,$y);
            println!("Inside rule 1: {}", stringify!(concat!($($tail)*)));
            vec_with_keys!($($tail)*); // here is the error
        }
    };
    (,$($tail:tt)*)=>{
        {
            println!("Inside rule 2: {}", stringify!(concat!($($tail)*)));
            vec_with_keys!($($tail)*);
        }
    };
    ($x:literal)=>{panic!("Incorrect number of arguments");};
}

fn main(){
   vec_with_keys![9:1,8:9];
}

The above code gives error missing tokens in macro arguments. However, the print statement shows that tokens are present. Not sure why it is not passed in macro invocation

This one works

macro_rules! vec_with_keys {
    ($x:literal $sep:tt $y:literal $($tail:tt)*) => {
        {
            println!("Value pair received: ({}, {})", $x,$y);
            println!("Inside rule 1: {}", stringify!(concat!($($tail)*)));
            // vec_with_keys!($($tail)*);
        }
    };
    (,$($tail:tt)*)=>{
        {
            println!("Inside rule 2: {}", stringify!(concat!($($tail)*)));
            vec_with_keys!($($tail)*);
        }
    };
    ($x:literal)=>{panic!("Incorrect number of arguments");};
}


fn main(){
   vec_with_keys![,8:9];
}

Never mind, I just added another rule ()=>{}
It works now.

I got it to work now. Any feedback will be appreciated. I am new to this and hence your feedback will help me learn more.

macro_rules! vec_with_keys {
    ($x:literal $sep2:tt $y:literal $($tail:tt)*) => {
        {
            let mut array = Vec::new();
            for _ in 0..$x{
                array.push(Default::default());
            }
            array.push($y);
            vec_with_keys!(array $($tail)*);
            array
        }
    };
    ($array:ident $sep1:tt $x:literal $sep2:tt $y:literal $($tail:tt)*) => {
        {
            if $array.len() < $x{
                for _ in $array.len()..$x{
                    $array.push(Default::default());
                }
                $array.push($y);
            } else {
                $array[$x]=$y;
            }
            vec_with_keys!($array $($tail)*);
        }
    };
    ($x:literal)=>{panic!("Incorrect number of arguments passed to macro vec_with_keys! Expected (key:value) pair but received only key");};
    ($array:ident) => {
    };
}

fn main(){
    let v = vec_with_keys![9:1,4:5];
    println!("{:?}",v);
}

For what it's worth, I would write it with repetitions in a single rule:

macro_rules! vec_with_keys {
    ($($pos:literal: $val:expr),+) => {
        {
            let capacity:usize = 1 + [$($pos),+].iter().copied().max().unwrap();
            let mut result = Vec::with_capacity(capacity);
            result.resize_with(capacity, Default::default);
            $( result[$pos] = $val; )+
            result
        }
    }
}

(Playground)

1 Like

You just had to made me realize I am a noob! :laughing:

It is amazing. Let me take a while to process your code. Thanks a lot. Many things to learn!

Now that I've got both versions in front of me, let me go through the differences:

  • Macros can match literal text, so I used : in place of your $sep1:tt
  • $(...),+ matches 1 or more ...s separated by ,
  • There can be multiple macro variables in a single repetition, and the macro body can use a repetition multiple times.
  • [$($pos),+].iter().copied().max().unwrap() is using iterator adapters to find the largest provided index. There might be a better way to do this, but it's what came to mind.
  • By calculating the size first, this code does only one heap allocation (the with_capacity and resize_with) lines.
  • Because the array has already been made the correct size, all of the given values can be written in the same way, with result[...] = ...
1 Like