generics - 使用宏编写 const 泛型枚举组合

标签 generics rust enums rust-macros

考虑一个带有两个 const 泛型参数的玩具结构:

pub struct Foo<const N: usize, const M: usize>([usize; N], [usize; M]);

impl<const N: usize, const M: usize> Foo<N, M> {
    pub fn bar(&self) -> usize {
        N * M
    }
}

假设 1 到 5 之间的所有 NM 组合都是允许的,这样我们就可以编写以下枚举:

pub enum FooEnum {
    Foo_1_1(Foo<1, 1>),
    Foo_1_2(Foo<1, 2>),
    Foo_2_1(Foo<2, 1>),
    Foo_2_2(Foo<2, 2>),
    // ... and so on.
}

impl FooEnum {
    pub fn bar(&self) -> usize {
        match self {
            Self::Foo_1_1(x) => x.bar(),
            Self::Foo_1_2(x) => x.bar(),
            Self::Foo_2_1(x) => x.bar(),
            Self::Foo_2_2(x) => x.bar(),
            // ... and so on.

        }
    }
}

我的问题是:我们可以编写一个声明性宏来生成它,而不需要手动写出所有组合吗?也就是说,类似 impl_foo_enum!(1, 2, 3, 4, 5),而不是 impl_foo_enum!(1;1, 1;2, 1;3, [...等等])


我可以使用paste crate 编写后一个宏:

macro_rules! impl_foo_enum {
    ($($n:literal;$m:literal),+) => {
        paste::paste! {
            pub enum FooEnum2 {
                $(
                    [<Foo _ $n _ $m>](Foo<$n, $m>)
                ),+
            }

            impl FooEnum2 {
                pub fn bar(&self) -> usize {
                    match self {
                        $(Self::[<Foo _ $n _ $m>](x) => x.bar()),+
                    }
                }
            }
        }
    }
}

impl_foo_enum!(1;1, 1;2, 2;1, 2;2);

( Playground )

为了获得不那么繁琐的宏,有几个相关的问题和有用的答案( 12 ),我认为我可以适应,但在这两种情况下,函数调用都可以在宏内重复,这似乎使事情变得简单。例如,使用第一个链接示例中的方法,我开始:

macro_rules! for_all_pairs {
    ($mac:ident: $($x:literal)*) => {
        for_all_pairs!(@inner $mac: $($x)*; $($x)*);
    };
    (@inner $mac:ident: ; $($x:literal)*) => {};
    (@inner $mac:ident: $head:literal $($tail:literal)*; $($x:literal)*) => {
        $(
            $mac!($head $x);
        )*
        for_all_pairs!(@inner $mac: $($tail)*; $($x)*);
    };
}

macro_rules! impl_foo_enum {
    ($n:literal $m:literal) => {
        paste::paste! { [<Foo _ $n _ $m>](Foo<$n, $m>) }
    }
}

pub enum FooEnum3 {
    for_all_pairs!(impl_foo_enum: 1 2)
}

( Playground )

这不会编译,因为编译器不希望在枚举变体位置有宏(我相信)。

(需要明确的是,我不一定想将上述内容用于任何严肃的事情,我只是在实验时遇到它并感到好奇。)

最佳答案

Here你走:

#![allow(non_camel_case_types)]
pub struct Foo<const N: usize, const M: usize>([usize; N], [usize; M]);
impl<const N: usize, const M: usize> Foo<N, M> {
    pub fn bar(&self) -> usize {
        N * M
    }
}

macro_rules! impl_foo_2{
    ($($n:literal)*) => {
        impl_foo_2!([] @orig($($n)*) ($($n)*) ($($n)*));
    };
    (
        [$(($n:literal $m:literal))*]
        @orig($($n_orig:literal)*)
        ($($n_unused:literal)*) ()
    ) => {
        paste::paste! {
            pub enum FooEnum2 {
                $([<Foo _ $n _ $m>](Foo<$n, $m>)),+
            }
            impl FooEnum2 {
                pub fn bar(&self) -> usize {
                    match self {
                        $(Self::[<Foo _ $n _ $m>](x) => x.bar()),+
                    }
                }
            }
        }
    };
    (
        [$($t:tt)*]
        @orig($($n_orig:literal)*)
        () ($m0:literal $($m:literal)*)
    ) => {
        impl_foo_2!(
            [$($t)*]
            @orig($($n_orig)*)
            ($($n_orig)*) ($($m)*)
        );
    };
    (
        [$($t:tt)*]
        @orig($($n_orig:literal)*)
        ($n0:literal $($n:literal)*) ($m0:literal $($m:literal)*)
    ) => {
        impl_foo_2!(
            [$($t)* ($n0 $m0)]
            @orig($($n_orig)*)
            ($($n)*) ($m0 $($m)*)
        );
    }
}

impl_foo_2!(1 2 3 4 5);

impl_foo_2 在内部生成号码列表的两个相同副本。然后,它会继续一次处理一个 m,并将其与每个 n 组合(它是通过重复砍掉第一个 n 来实现的) 。如果n列表耗尽,它会重置n列表,并砍掉第一个m。所有这些都会完成,直到所有 nm 都耗尽。

中间结果被收集到宏的第一个参数中,最后该参数被传递到您的 impl_foo_enum

关于generics - 使用宏编写 const 泛型枚举组合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67038789/

相关文章:

java - 如何在 Java 中序列化泛型类?

rust - 如何使用包含返回对 Self 的引用的方法的特征对象?

enums - 如何在 Swift 中指定类型约束为枚举?

C# In() 方法? (像 SQL)

c# - 多泛型歧义

c# - 错误 "Unable to cast object of type ' System.Int3 2' to type ' System.Collections.Generic.List`1[System.Int32 ]'"

swift - Swift is Operator 的意外结果

multithreading - 我如何从另一个线程使用 hyper::client?

rust - 为什么我不能可变地移动捕获不可变变量?

java - 何时以及为何使用 JumboEnumSet