genericsrustmacros

Enumeration inside macro in Rust


I have the following rust code

use std::any::Any;
use std::rc::Rc;

fn main() {
    let mut variables = Vec::<Rc<dyn Any>>::new();
    variables.push(Rc::new(1_i32));
    variables.push(Rc::new(false));
    variables.push(Rc::new(4.53_f64));

    let integer: i32 = *variables[0].clone().downcast::<i32>().unwrap();
    let boolean: bool = *variables[1].clone().downcast::<bool>().unwrap();
    let floating: f64 = *variables[2].clone().downcast::<f64>().unwrap();

    let my_goal: (i32, bool, f64) = (integer, boolean, floating)

    println!("{:?}", my_goal);

which I want to automatize using a macro. (I know that this is suboptimal, but this is just a MWE to ask a question about macros)

In particular I want to create a tuple downcasting Any to given types

My first attempt is:

macro_rules! my_macro {
    ($vector:ident; $($type:ty),*) => {
        // ($($type),*)
        //println!("{:?}", $vector);
        //$(println!("{:?}", stringify!($type));)*
        let idx = 0;
        ($(
                *$vector[0].clone().downcast::<$type>().unwrap()
        ),*)
    };
}

which is wrong because of the 0 hardcoded. I tried several way to declare a variable and increment it along the macro but they failed. How can I do?


Solution

  • I am pretty sure you failed to declare the variable because you only used a single set of curly braces when actually two would be required inside the macro after the fat arrow.

    I suppose this is the macro you are after:

    macro_rules! tuple_downcast {
        ($vector:ident; ($($type:ty),*)) => {{
            let mut iter = $vector.into_iter();
            (
                $(
                    *iter.next()
                        .expect("The vector contains less elements than the type tuple.")
                        .clone()
                        .downcast::<$type>()
                        .expect("The value can not be downcasted to the specified type."),
                )*
            )
        }};
    }
    

    you can use it like this:

    use std::any::Any;
    use std::rc::Rc;
    
    macro_rules! tuple_downcast {
        ($vector:ident; ($($type:ty),*)) => {{
            let mut iter = $vector.into_iter();
            (
                $(
                    *iter.next()
                        .expect("The vector contains less elements than the type tuple.")
                        .clone()
                        .downcast::<$type>()
                        .expect("The value can not be downcasted to the specified type."),
                )*
            )
        }};
    }
    
    fn main() {
        let mut variables = Vec::<Rc<dyn Any>>::new();
        variables.push(Rc::new(1_i32));
        variables.push(Rc::new(false));
        variables.push(Rc::new(4.53_f64));
    
        let integer: i32 = *variables[0].clone().downcast::<i32>().unwrap();
        let boolean: bool = *variables[1].clone().downcast::<bool>().unwrap();
        let floating: f64 = *variables[2].clone().downcast::<f64>().unwrap();
    
        let my_goal: (i32, bool, f64) = (integer, boolean, floating);
    
        println!("{:?}", my_goal);
    
        let mut variables = Vec::<Rc<dyn Any>>::new();
        variables.push(Rc::new(1_i32));
        variables.push(Rc::new(false));
        variables.push(Rc::new(4.53_f64));
    
        let my_goal = tuple_downcast!(variables; (i32, bool, f64));
    
        println!("{:?}", my_goal);
    }
    

    Which results in the following output:

    (1, false, 4.53)
    (1, false, 4.53)
    

    Alternatively if you absolutely want to use an index for accessing the elements:

    macro_rules! tuple_downcast {
        ($vector:ident; ($($type:ty),*)) => {{
            let mut idx = 0;
            (
                $({
                    let value = *$vector[idx].clone()
                        .downcast::<$type>()
                        .unwrap();
                    idx += 1;
    
                    value
                }),*
            )
        }};
    }