【问题标题】:Generating tuple indices based on macro_rules! repetition expansion基于 macro_rules 生成元组索引!重复扩展
【发布时间】:2021-02-27 08:08:55
【问题描述】:

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

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

  • 我尝试使用空的“替换”内部宏,例如 (@replace $x:tt) => { $x; },但这在元组构造中不起作用。
  • 我意识到我可以将三个语句合并为一个,但在我的实际宏中,我也需要中间变量。
  • 我基本上是在寻找与 C++ 的std::integer_sequence 等效的版本
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,但看起来它已被搁置。

【问题讨论】:

    标签: rust macros tuples


    【解决方案1】:

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

    • exprs 的序列(称为 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,);
    }
    

    【讨论】:

    • 哇,这真是令人费解的东西!感谢您抽出宝贵的时间,将尝试使用它 :) 此外,我认为这是在声明性宏中建立索引的明确案例;)
    【解决方案2】:

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

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

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

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

    【讨论】:

    • 嗯,从来没有意识到您可以以这种方式重用重复变量,从而隐藏原始名称。不幸的是,这对我来说是行不通的,因为我稍后也需要访问中间变量,我也不能使用数组,因为 a、b 和 c 的类型在我的真实代码中实际上是通用的。 :/ 尽管如此,感谢您抽出宝贵时间回答!
    • @StijnFrishert 也许您可以通过将中间变量打包到自己的结构中来规避中间变量限制(请参阅更新的答案)。
    猜你喜欢
    • 2011-05-21
    • 1970-01-01
    • 2012-01-18
    • 2021-11-20
    • 1970-01-01
    • 1970-01-01
    • 2019-11-01
    • 2021-09-08
    • 1970-01-01
    相关资源
    最近更新 更多