Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

macro with variable arguments of different types

Tags:

macros

rust

I'm trying to create macros to make is simple to create small instances of a custom tree-type. For this I'd like to specify children either as nodes or integers (without explicitly turning them into a Node type). My attempt is below, which fails as the type of $x resolves as MDTree, with the message expected enum MDTree, found integral variable.

pub struct MultiNode {
    children: Vec<MDTree>
}
impl MultiNode {
    pub fn new(children: Vec<MDTree>) -> Box<MultiNode> {
        return Box::new(MultiNode { children: children });
    }
}
pub enum MDTree {
    Single(u32),
    Prime(Box<MultiNode>),
    Degenerate(Box<MultiNode>),
}
macro_rules! mod_single {
    ($x:expr) => { MDTree::Single($x) }
}
macro_rules! mod_multi {
    ($($x:expr),*) => {{
        let mut children: Vec<MDTree> = Vec::new();
        $(
            match $x {
                0...4294967295 => { children.push(mod_single!($x)); }
                _ => { children.push($x); }
            }
        )*
        MultiNode::new(children)
    }}
}
macro_rules! mod_prime {
    ($($x:expr),*) => { MDTree::Prime(mod_multi!($($x),*)) }
}
macro_rules! mod_degen {
    ($($x:expr),*) => { MDTree::Degenerate(mod_multi!($($x),*)) }
}
fn main() {
    let md: MDTree = mod_prime!(0, mod_degen!(1,2));
}

Is there any way to fix this without having to write mod_prime!(mod_single(0), mod_degen!(mod_single(1),mod_single(2))) or similar?

like image 809
Henning Koehler Avatar asked Dec 12 '16 03:12

Henning Koehler


2 Answers

A macro cannot reason about the type of its operands, because macro expansion happens before type resolution.

That doesn't mean there's no solution, though! Indeed, the macro can expand to code whose behavior varies based on the expression's type. How do we do that? With traits, of course! We'll use the From and Into traits from the standard library here, but you can define your own trait if you prefer.

Let's first see how the mod_multi! macro looks like when using traits:

macro_rules! mod_multi {
    ($($x:expr),*) => {{
        let mut children: Vec<MDTree> = Vec::new();
        $(
            children.push($x.into());
        )*
        MultiNode::new(children)
    }}
}

The key point here is that the macro doesn't try to figure out the type of $x; it'll be figured out later on by the compiler, and the compiler will dispatch the call to into() based on that type. That does mean that if you pass an unsupported argument to the macro, you'll get compiler errors that might be unclear at first sight.

Now, we need to implement Into in order for both u32 and MDTree to be accepted by the macro. Into has a blanket implementation based on the From trait, so we should implement From instead of Into. The standard library already provides impl<T> From<T> for T, so we can already pass an MDTree to the macro. That just leaves u32. The implementation goes like this:

impl From<u32> for MDTree {
    fn from(value: u32) -> MDTree {
        mod_single!(value)
    }
}

And now the macro invocation works as intended!

like image 169
Francis Gagné Avatar answered Nov 09 '22 22:11

Francis Gagné


While the answer by Francis Gagné may be more directly an answer to the question you asked, I just want to highlight the fact that macros also let you build trees like these with quite minimal syntax if you so desire (since you are after all asking about building trees with macros):

#[derive(Debug)]
pub enum MDTree {
    Single(u32),
    Prime(Vec<MDTree>),
    Degenerate(Vec<MDTree>),
}

macro_rules! mdtree {
    ([$($sub:tt),*]) => {{
        MDTree::Prime(vec!($(mdtree!($sub),)*))
    }};
    ({$($sub:tt),*}) => {{
        MDTree::Degenerate(vec!($(mdtree!($sub),)*))
    }};
    ($e:expr) => {
        MDTree::Single($e)
    };
}

fn main() {
    println!("{:?}", mdtree!([1, [2, 3], {4, 5}]));
}

Playground

You can use (, { and [ as different variants, but beyond two using some actual identifiers are probably a good idea anyway.

like image 44
Erik Vesteraas Avatar answered Nov 09 '22 21:11

Erik Vesteraas