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

时间:2018-08-22 11:35:47

标签: macros rust

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

为此,我编写了一个宏,该宏采用新类型的名称和该点的尺寸(据我所知,Rust不允许数值泛型)。

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

我这样使用它:

define_point!(Point2, 2);
define_point!(Point3, 3);

这很好。我还在此宏中的Point类型上实现了Index特征,以直接访问坐标。

现在,我需要一些方便的功能来访问我的点的坐标,如下所示: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), +);
    }
}

我按如下方式使用它:

impl_point_accessors!(Point2, x, 0, y, 1);
impl_point_accessors!(Point3, x, 0, y, 1, z, 2);

当我查看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
    };
}

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

impl_point_accessors!($type_name, dimension_to_coord_pairs!($dimension));

似乎dimension_to_coord_pairs宏没有扩展到我想要的参数列表中。

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

2 个答案:

答案 0 :(得分:1)

  

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

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

您可以获得的最接近的是使用“回调”样式:

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,));

答案 1 :(得分:1)

一个宏可以调用另一个宏,但是一个宏不能调用另一个宏的结果。每次宏调用都必须产生合法的代码,该代码可以包括其他宏调用,但是编译器永远不必弄清楚首先要调用哪个宏。

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

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);
    };
}

虽然没有您尝试的那么整洁,但是仍然可以让您以自己想要的方式调用它:

define_point!(Point3, 3);

请注意,我将$dimension: expr更改为$dimension: tt。我不是100%知道为什么会这样,但是在宏中,expr类型的变量不能与文字匹配。

此外,我将返回类型更改为&T而不是T。您也可以改用T: Copy来解决相同的问题。