Reputation: 4542
I am trying to implement a generic Point<T>
type for small dimensions.
To achieve that, I wrote a macro that takes the name of the new type and the dimension of the point (since, as far as I know, Rust doesn't allow for numerical generics).
macro_rules! define_point {
($type_name: ident, $dimension: expr) => {
pub struct $type_name<T> {
coords: [T; $dimension]
}
}
}
I use it like this:
define_point!(Point2, 2);
define_point!(Point3, 3);
This works fine. I also implement the Index
trait on my Point type in this macro to access the coordinates directly.
Now I want some convenience functions for accessing the coordinates of my point as follows: p.x()
, p.y()
or p.z()
depending on the dimension.
To do that, I have another macro:
macro_rules! impl_point_accessors {
($type_name: ident, $coord_name: ident, $coord_index: expr) => {
impl<T> $type_name<T> {
pub fn $coord_name(&self) -> T {
&self[$coord_index]
}
}
};
($type_name: ident, $coord_name: ident, $coord_index: expr, $($extra_coord_name: ident, $extra_coord_index: expr),+) => {
impl_point_accessors!($type_name, $coord_name, $coord_index);
impl_point_accessors!($type_name, $($extra_coord_name, $extra_coord_index), +);
}
}
I use it as follows:
impl_point_accessors!(Point2, x, 0, y, 1);
impl_point_accessors!(Point3, x, 0, y, 1, z, 2);
This seems to work when I look at the result of rustc --pretty=expanded
.
Now, as an exercise, I wrote this other macro that would give me the list x, 0, y, 1, ...
from the dimension directly:
macro_rules! dimension_to_coord_pairs {
(1) => {
x, 0
};
(2) => {
x, 0, y, 1
};
(3) => {
x, 0, y, 1, z, 2
};
(4) => {
x, 0, y, 1, z, 2, w, 3
};
}
However, when I try to use the output of this new macro like this:
impl_point_accessors!($type_name, dimension_to_coord_pairs!($dimension));
It looks like the dimension_to_coord_pairs
macro does not get expanded into the list of arguments that I want.
My question now: Is there any way to tell Rust to expand the macro and use the expanded syntax as my list of arguments in another macro ?
Upvotes: 14
Views: 7423
Reputation: 58735
A macro can invoke another macro, but one cannot take the result of another. Each macro invocation must result in legal code, which can include other macro invocations, but the compiler should never have to figure out which macro you intended to be invoked first.
You can work around your problem by reorganising the macros to be completely top-down, something like this:
macro_rules! define_point {
($type_name: ident, $dimension: tt) => {
pub struct $type_name<T> {
coords: [T; $dimension]
}
impl_point_accessors!($type_name, $dimension);
}
}
macro_rules! impl_point_accessors {
($type_name: ident, $dimension: tt) => {
impl<T> $type_name<T> {
write_coord_getters!($dimension);
}
};
}
macro_rules! coord_getter {
($coord_name: ident, $coord_index: expr, $ret: ty) => {
pub fn $coord_name(&self) -> &T {
&self.coords[$coord_index]
}
}
}
macro_rules! write_coord_getters {
(1) => {
coord_getter!(x, 1, T);
};
(2) => {
write_coord_getters!(1);
coord_getter!(y, 2, T);
};
(3) => {
write_coord_getters!(2);
coord_getter!(z, 3, T);
};
(4) => {
write_coord_getters!(3);
coord_getter!(w, 4, T);
};
}
It's not quite as tidy as you were attempting, but it still lets you invoke it the way you wanted:
define_point!(Point3, 3);
Notice that I changed $dimension: expr
to $dimension: tt
. I'm not 100% sure why this is the case but, inside a macro, a variable of type expr
cannot match a literal.
Also, I changed the return type to &T
instead of T
. You could also fix the same problem by making T: Copy
instead.
Upvotes: 3
Reputation: 59005
My question now: Is there any way to tell Rust to expand the macro and use the expanded syntax as my list of arguments in another macro ?
No. Macros are syntactic, not lexical. That is, a macro cannot expand to an arbitrary bundle of tokens. Even if it could, you would need some way to force the compiler to expand the inner macro before the outer one, and you can't do that either.
The closest you can get is to use a "callback" style:
macro_rules! impl_point_accessors {
($type_name: ident, $coord_name: ident, $coord_index: expr) => {
impl<T> $type_name<T> {
pub fn $coord_name(&self) -> T {
panic!("coord {}", $coord_index);
}
}
};
($type_name: ident, $coord_name: ident, $coord_index: expr, $($extra_coord_name: ident, $extra_coord_index: expr),+) => {
impl_point_accessors!($type_name, $coord_name, $coord_index);
impl_point_accessors!($type_name, $($extra_coord_name, $extra_coord_index), +);
}
}
macro_rules! dimension_to_coord_pairs {
(1, then $cb:ident!($($cb_args:tt)*)) => {
$cb!($($cb_args)* x, 0);
};
(2, then $cb:ident!($($cb_args:tt)*)) => {
$cb!($($cb_args)* x, 0, y, 1);
};
(3, then $cb:ident!($($cb_args:tt)*)) => {
$cb!($($cb_args)* x, 0, y, 1, z, 2);
};
(4, then $cb:ident!($($cb_args:tt)*)) => {
$cb!($($cb_args)* x, 0, y, 1, z, 2, w, 3);
};
}
struct Point<T>(Vec<T>);
dimension_to_coord_pairs!(2, then impl_point_accessors!(Point,));
Upvotes: 2