【问题标题】:macro with variable arguments of different types具有不同类型的可变参数的宏
【发布时间】:2016-12-12 03:49:51
【问题描述】:

我正在尝试创建宏,以便轻松创建自定义树类型的小实例。为此,我想将子节点指定为节点或整数(没有明确地将它们转换为 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))) 或类似的东西?

【问题讨论】:

    标签: macros rust


    【解决方案1】:

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

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

    我们先看看mod_multi!宏在使用traits时的样子:

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

    这里的关键是宏不会试图找出$x的类型;稍后编译器会发现它,编译器会根据该类型将调用分派给into()。这确实意味着,如果您将不受支持的参数传递给宏,您将获得乍一看可能不清楚的编译器错误。

    现在,我们需要实现Into 以使u32MDTree 都被宏接受。 Into 有一个基于 From 特征的全面实现,所以我们应该实现 From 而不是 Into。标准库已经提供了impl&lt;T&gt; From&lt;T&gt; for T,所以我们已经可以将MDTree 传递给宏。那只剩下u32。实现是这样的:

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

    现在宏调用按预期工作!

    【讨论】:

      【解决方案2】:

      虽然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

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

      【讨论】:

      • 非常整洁 - 更紧凑,更少宏观混乱。对于更多节点类型/提高可读性,您可以随时添加字符作为模式的一部分,例如 mdtree!([1, P[2, 3], D[4, 5]) ...
      • 实际上,添加 P[2,3]D[4,5] 似乎不起作用 - 我猜这是因为它们没有形成单个令牌树?
      • 是的,所以据我所知,如果你想添加标识符,你必须这样做 [P 2, 3] [D 4, 5]
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-23
      • 1970-01-01
      • 2011-04-03
      • 2023-03-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多