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

Hen*_*ler 5 macros rust

我正在尝试创建宏,以便创建自定义树类型的小实例.为此,我想将子节点指定为节点或整数(没有明确地将它们转换为Node类型).我的尝试在下面,它与消息的$x解析类型一样失败.MDTreeexpected 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));
}
Run Code Online (Sandbox Code Playgroud)

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

Fra*_*gné 8

宏不能推断其操作数的类型,因为宏扩展在类型解析之前发生.

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

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

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

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

现在,我们需要实现Into两者u32并被MDTree宏接受.Into有一个基于From特征的毯子实现,所以我们应该实现From而不是Into.标准库已经提供了impl<T> From<T> for T,所以我们已经可以传递MDTree给宏了.那就是离开u32.实现如下:

impl From<u32> for MDTree {
    fn from(value: u32) -> MDTree {
        mod_single!(value)
    }
}
Run Code Online (Sandbox Code Playgroud)

现在宏调用按预期工作!


Eri*_*aas 6

虽然FrancisGagné的答案可能更直接地回答了你提出的问题,但我只想强调一下,如果你愿意的话,宏也会让你用非常小的语法构建这样的树(因为你毕竟是在询问用宏构建树):

#[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}]));
}
Run Code Online (Sandbox Code Playgroud)

操场

您可以使用(,{并且[作为不同的变体,但超过两个使用一些实际的标识符无论如何都可能是一个好主意.