基于 macro_rules 生成元组索引!重复扩展

Generating tuple indices based on macro_rules! repetition expansion

给定一个带有任意数量参数的声明性 macro_rules! 宏,我如何根据重复扩展生成元组索引?

在下面的示例中,我希望生成第 6 行和第 7 行的 .0

macro_rules! the_action {
    (@replace $x:tt) => {$x;};
    
    ($($queue:expr),+) => {
        let iters = ($($queue.iter()),+);
        let options = ($(iters.0.next())*);
        let values = ($(options.0.unwrap_or_default())*);
        
    }
}

fn main() {
    let a = [1, 4];
    let b = [3, 2];
    let c = [5, -1];
    
    the_action!(a);
    the_action!(a, b);
    the_action!(a, b, c);
}

Playground link


奖金问题:鉴于 Rust 中缺少可变参数泛型,我希望这在 Rust 中很常见。为什么 macro_rules 中没有用于重复索引的内置语法?编译器肯定有可用的信息。这是否曾在 RFC 中讨论过?

编辑:显然 it has,但看起来它已被搁置。

据我所知,使用声明性宏无法生成它们。

这取决于您的用例,但就目前而言,您可以从以下开始,每次都解包元组:

macro_rules! the_action {
    (@replace $x:tt) => {$x;};
    
    ($($queue:ident),+) => {
        let ($(mut $queue,)+) = ($($queue.iter().copied(),)+);
        let ($($queue,)+) = ($($queue.next(),)+);
        let values = ($($queue.unwrap_or_default(),)*);
        
    }
}

fn main() {
    let a = [1, 4];
    let b = [3, 2];
    let c = [5, -1];
    
    the_action!(a);
    the_action!(a, b);
    the_action!(a, b, c);
}

如果您需要访问中间步骤,您可以将它们打包到不同的结构中,如:

macro_rules! the_action {
    (@replace $x:tt) => {$x;};
    
    ($($queue:ident),+) => {
        {
            struct Step1<$($queue,)+> {
                $($queue: $queue,)+
            }
            let mut step1 = Step1{$($queue: $queue.iter().copied(),)+};
            struct Step2<$($queue,)+> {
                $($queue: $queue,)+
            }
            let step2 = Step2{$($queue: step1.$queue.next(),)+};
            let values = ($(step2.$queue.unwrap_or_default(),)*);
        }    
    }
}

fn main() {
    let a = [1, 4];
    let b = [3, 2];
    let c = [5, -1];
    
    the_action!(a);
    the_action!(a, b);
    the_action!(a, b, c);
}

一种替代方法是将元组解压缩到一个数组中,并使用索引——这甚至不需要在宏观上创建。

这是另一个例子,即一个宏 tpl_map,它对每个元组元素应用一个操作,产生另一个元组。它期望:

  • 一系列 expr(称为 queue 以匹配您的示例)以确定元组组件的数量。
  • 标识符 tpl 表示要映射其元素的元组。
  • 描述元组操作的标识符fn(应该引用接受一个参数的宏)。 我尝试在那里接受一个闭包,但这导致我出现“需要类型注释”错误,所以我使用了一个宏。

由于很难(不可能?)在声明性宏中生成元组索引,目前它被限制为最多具有 5 个元素的元组,但这可以通过更改一行来扩展(参见 [(0) (1) (2) (3) (4)]).

此外,我不得不求助于 apply 拐杖(它 - 天真地 - 应该是内联的)来编译它。

macro_rules! apply {
    ($f:ident, $e:expr) => {$f!($e)}
}

macro_rules! tpl_map {
    (@, [], [$(($idx:tt))*], $tpl:ident, $fn:ident, ($($result:tt)*)) => {($($result)*)};
    (@, [$queue0:expr, $($queue:expr,)*], [($idx0:tt) $(($idx:tt))*], $tpl:ident, $fn:ident, ($($result:tt)*)) => {
        tpl_map!(@,
            [$($queue,)*],
            [$(($idx))*],
            $tpl,
            $fn,
            ($($result)* apply!($fn, ($tpl . $idx0)), )
        )
    };
    ([$($queue:expr,)*], $tpl:ident, $fn:ident) => {
        tpl_map!(@,
            [$($queue,)*],
            [(0) (1) (2) (3) (4)],
            $tpl,
            $fn,
            ()
        )
    }
}

macro_rules! the_action { ($($queue:expr,)+) => {
    let mut iters = ($($queue.iter(),)+);
    macro_rules! iter_next{($elem:expr) => {
        $elem .next()
    }}
    let options = tpl_map!([$($queue,)*], iters, iter_next);
    macro_rules! unwrap_or_default{($elem:expr) => {
        $elem .copied() .unwrap_or_default()
    }}
    let values = tpl_map!([$($queue,)*], options, unwrap_or_default);
}}

fn main() {
    let a = [1usize, 4];
    let b = [3u8, 2];
    let c = [true, false];
    
    the_action!(a, b, c,);
}