#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 Возможно, вы можете обойти ограничение промежуточной переменной, упаковав их в собственные структуры (см. Обновленный ответ).