使用宏的输出作为另一个宏的参数

Sun*_*eef 5 macros rust

我正在尝试Point<T>为小尺寸实现通用类型。

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

macro_rules! define_point {
($type_name: ident, $dimension: expr) => {
        pub struct $type_name<T> {
            coords: [T; $dimension]
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我像这样使用它:

define_point!(Point2, 2);
define_point!(Point3, 3);
Run Code Online (Sandbox Code Playgroud)

这工作正常。我还在Index这个宏中在我的 Point 类型上实现了trait 来直接访问坐标。

现在我想要一些方便的函数来访问我的点的坐标,如下所示: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), +);
    }
}
Run Code Online (Sandbox Code Playgroud)

我使用它如下:

impl_point_accessors!(Point2, x, 0, y, 1);
impl_point_accessors!(Point3, x, 0, y, 1, z, 2);
Run Code Online (Sandbox Code Playgroud)

当我查看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
    };
}
Run Code Online (Sandbox Code Playgroud)

但是,当我尝试像这样使用这个新宏的输出时:

impl_point_accessors!($type_name, dimension_to_coord_pairs!($dimension));
Run Code Online (Sandbox Code Playgroud)

看起来dimension_to_coord_pairs宏没有扩展到我想要的参数列表中。

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

Pet*_*all 3

一个宏可以调用另一个宏,但不能获取另一个宏的结果。每个宏调用都必须生成合法的代码,其中可以包含其他宏调用,但编译器永远不必弄清楚您打算首先调用哪个宏。

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

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);
    };
}
Run Code Online (Sandbox Code Playgroud)

它并不像您尝试的那么整洁,但它仍然允许您按照您想要的方式调用它:

define_point!(Point3, 3);
Run Code Online (Sandbox Code Playgroud)

请注意,我更改$dimension: expr$dimension: tt. 我不是 100% 确定为什么会出现这种情况,但是在宏内部,类型变量expr不能与文字匹配。

另外,我将返回类型更改为&T而不是T. 您也可以通过 make 来解决同样的问题T: Copy

  • 我认为不幸的是,消耗内部宏调用的外部宏调用无法选择要求编译器在继续之前扩展内部调用。此功能存在于 Lisp 宏系统中,并且在某些情况下对于编写正确组合的宏是必需的。如果外部宏想要根据内部宏的参数更改行为(假设内部有一个名称参数,并且外部正在收集所有内部宏调用的所有名称),那么外部宏只能检查名称,只要名称本身未通过宏调用指定。 (2认同)