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?
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!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With