Macros 将宏的输出用作另一个宏的参数

Macros 将宏的输出用作另一个宏的参数,macros,rust,Macros,Rust,我正在尝试为小尺寸实现一个通用的点类型 为了实现这一点,我编写了一个宏,它采用了新类型的名称和点的维度(因为,据我所知,Rust不允许使用数字泛型) macro\u规则!定义_点{ ($type_name:ident,$dimension:expr)=>{ 发布结构$type\u名称{ 坐标:[T;$dimension] } } } 我是这样使用它的: define_point!(Point2, 2); define_point!(Point3, 3); impl_point_accesso

我正在尝试为小尺寸实现一个通用的
类型

为了实现这一点,我编写了一个宏,它采用了新类型的名称和点的维度(因为,据我所知,Rust不允许使用数字泛型)

macro\u规则!定义_点{
($type_name:ident,$dimension:expr)=>{
发布结构$type\u名称{
坐标:[T;$dimension]
}
}
}
我是这样使用它的:

define_point!(Point2, 2);
define_point!(Point3, 3);
impl_point_accessors!($type_name, dimension_to_coord_pairs!($dimension));
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);
    };
}
这个很好用。我还在这个宏中的点类型上实现
索引
特性,以便直接访问坐标

现在我需要一些方便的函数来访问我点的坐标,如下所示:
p.x()
p.y()
p.z()
,具体取决于尺寸

为此,我有另一个宏:

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), +);
    }
}
当我查看
rustc--pretty=expanded
的结果时,这似乎是有效的

现在,作为练习,我编写了另一个宏,它将直接从维度中为我提供列表
x,0,y,1,…

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
    };
}
但是,当我尝试像这样使用新宏的输出时:

define_point!(Point2, 2);
define_point!(Point3, 3);
impl_point_accessors!($type_name, dimension_to_coord_pairs!($dimension));
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);
    };
}
看起来,
维度到坐标对
宏没有扩展到我想要的参数列表中

我现在的问题是:有没有办法告诉Rust展开宏并将展开的语法用作另一个宏中的参数列表

我现在的问题是:有没有办法告诉Rust展开宏并将展开的语法用作另一个宏中的参数列表

不。宏是语法的,不是词汇的。也就是说,宏不能扩展到任意的令牌包。即使可以,您也需要某种方法来强制编译器在外部宏之前展开内部宏,而您也不能这样做

最接近的方法是使用“回调”样式:

macro\u规则!执行点存取器{
($type\u name:ident,$coord\u name:ident,$coord\u index:expr)=>{
impl$type\u名称{
发布fn$coord_name(&self)->T{
恐慌!(“coord{},$coord_指数);
}
}
};
($type\u name:ident,$coord\u name:ident,$coord\u index:expr,$($extra\u coord\u name:ident,$extra\u coord\u index:expr),+)=>{
impl_point_访问器!($type_name、$coord_name、$coord_index);
impl_point_访问器!($type_name,$($extra_coord_name,$extra_coord_index),+);
}
}
宏规则!尺寸对坐标对{
(1,然后是$cb:ident!($($cb_args:tt)*)=>{
$cb!($($cb_args)*x,0);
};
(2,然后是$cb:ident!($($cb_args:tt)*)=>{
$cb!($($cb_args)*x,0,y,1);
};
(3,然后是$cb:ident!($($cb_args:tt)*)=>{
$cb!($($cb_args)*x,0,y,1,z,2);
};
(4,然后是$cb:ident!($($cb_args:tt)*)=>{
$cb!($($cb_args)*x,0,y,1,z,2,w,3);
};
}
结构点(Vec);
尺寸对坐标对!(2,然后执行点存取器!(点,);

一个宏可以调用另一个宏,但不能接受另一个宏的结果。每个宏调用都必须生成合法代码,其中可以包含其他宏调用,但编译器永远不必确定要首先调用哪个宏

您可以通过将宏重新组织为完全自上而下的方式来解决问题,如下所示:

define_point!(Point2, 2);
define_point!(Point3, 3);
impl_point_accessors!($type_name, dimension_to_coord_pairs!($dimension));
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);
    };
}
请注意,我将
$dimension:expr
更改为
$dimension:tt
。我不能100%确定为什么会出现这种情况,但是在宏中,
expr
类型的变量不能匹配文本


另外,我将返回类型改为
&T
,而不是
T
。您还可以通过改为
T:Copy
来解决相同的问题。

我不100%确定为什么…:因为令牌匹配只在实际令牌上进行。一旦您捕获的内容不是
ident
tt
,它就不再是一个标记,而是一个语法树。我认为很不幸的是,正在使用内部宏调用的外部宏调用不能选择加入,请编译器在继续之前展开内部调用。lisp宏系统中存在此功能,在某些情况下,编写正确组合的宏时需要此功能。如果外部宏希望基于内部宏的参数更改行为(例如,内部有一个名称参数,外部正在收集所有内部宏调用中的所有名称),那么外部宏只能检查名称,只要名称本身没有通过宏调用指定。