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 ?
Just type the word Call then space, then type the name of the macro to be called (run). The example below shows how to call Macro2 from Macro1. It's important to note that the two macros DO NOT run at the same time. Once the Call line is hit, Macro2 will be run completely to the end.
To do that, you create what are called arguments. You come up with a name for a variable that you can use in the macro and put it in the middle of the parentheses at the top of the macro. This argument can then be used within the macro as a regular variable.
There are two distinct approaches to passing parameters to macros.
Yes, you can assign a macro to a button (or other excel controls/menu actions) and pass constant OR variable arguments to it. In the 'Assign Macro' window (right-click on object and select 'Assign Macro'): Enclose the macro name in single quotes e.g. to pass 2 constants: 'Button1_Click("A string!", 7)'
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.
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,));
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