我正在尝试创建宏,以便创建自定义树类型的小实例。为此,我想将子节点指定为节点或整数(没有明确地将它们转换为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)))
或类似内容?
答案 0 :(得分:8)
宏无法推断其操作数的类型,因为宏扩展在类型解析之前发生。
但这并不意味着没有解决方案!实际上,宏可以扩展到代码,其行为根据表达式的类型而变化。我们怎么做?当然还有特质!我们会在此处使用标准库中的From
和Into
特征,但如果您愿意,可以定义自己的特征。
让我们首先看看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
,以便宏接受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)
}
}
现在宏调用按预期工作!
答案 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}]));
}
您可以使用(
,{
和[
作为不同的变体,但是使用一些实际的标识符超过两个可能是一个好主意。