具有不同类型的可变参数的宏

时间:2016-12-12 03:49:52

标签: macros rust

我正在尝试创建宏,以便创建自定义树类型的小实例。为此,我想将子节点指定为节点或整数(没有明确地将它们转换为Node类型)。 My attempt位于下方,因$x类型解析为MDTree而失败,消息为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));
}

有没有办法解决这个问题而无需编写mod_prime!(mod_single(0), mod_degen!(mod_single(1),mod_single(2)))或类似内容?

2 个答案:

答案 0 :(得分:8)

宏无法推断其操作数的类型,因为宏扩展在类型解析之前发生。

但这并不意味着没有解决方案!实际上,宏可以扩展到代码,其行为根据表达式的类型而变化。我们怎么做?当然还有特质!我们会在此处使用标准库中的FromInto特征,但如果您愿意,可以定义自己的特征。

让我们首先看看mod_multi!宏在使用特征时的样子:

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

这里的关键点是宏并没有试图找出$x的类型;它将在稍后由编译器计算出来,编译器将根据该类型将调用分派给into()。这确实意味着如果您将不受支持的参数传递给宏,您将会收到可能一眼就看不清的编译器错误。

现在,我们需要实施Into,以便宏接受u32MDTreeInto基于From特征进行了全面实施,因此我们应该实施From而不是Into。标准库已提供impl<T> From<T> for T,因此我们已经可以将MDTree传递给宏。那就是u32。实现如下:

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

现在宏调用按预期工作!

答案 1 :(得分:6)

虽然the answer by Francis Gagné可能更直接地回答了您提出的问题,但我只想强调一下,如果您愿意,宏也可以让您使用非常小的语法构建这样的树(因为您之后所有关于用宏构建树的问题):

#[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

您可以使用({[作为不同的变体,但是使用一些实际的标识符超过两个可能是一个好主意。