Генерация индексов кортежей на основе macro_rules! расширение повторения

#rust #macros #tuples

#Ржавчина #макросы #кортежи

Вопрос:

Учитывая декларативный macro_rules! макрос, который принимает произвольное количество аргументов, как мне генерировать индексы кортежей на основе расширения повторения?

В приведенном ниже примере я бы хотел .0 , чтобы были сгенерированы строки 6 и 7.

  • Я пытался использовать пустой внутренний макрос «заменить», такой как (@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);
}
 

Ссылка на игровую площадку


Бонусный вопрос: учитывая отсутствие вариационных обобщений в Rust, я ожидаю, что это обычное дело в Rust. Почему в macro_rules нет встроенного синтаксиса для индексации повторений? У компилятора наверняка есть доступная информация. Обсуждалось ли это когда-либо в RFC?

Редактировать: по-видимому, это так, но, похоже, оно было приостановлено.

Ответ №1:

Вот еще один пример, а именно макрос tpl_map , который применяет операцию к каждому элементу кортежа, получая другой кортеж. Он ожидает:

  • Последовательность expr s (вызываемая 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,);
}
 

Комментарии:

1. Вау, это какая-то умопомрачительная штука! Спасибо, что нашли время, попробую поработать с этим 🙂 Более того, я думаю, что это явный пример индексации в декларативных макросах 😉

Ответ №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);
}
 

Одной из альтернатив было бы распаковать кортежи в массив и использовать индексы, которые даже не нужно было бы создавать с помощью макросов.

Комментарии:

1. Хммм, никогда не думал, что вы можете повторно использовать переменную повторения таким образом, затеняя исходные имена. К сожалению, для меня это неосуществимо, так как позже мне также понадобится доступ к промежуточным переменным, и я не могу использовать массивы, потому что типы a, b и c на самом деле являются общими в моем реальном коде. :/ Тем не менее, спасибо, что нашли время ответить!

2. @StijnFrishert Возможно, вы можете обойти ограничение промежуточной переменной, упаковав их в собственные структуры (см. Обновленный ответ).