Создайте сумму единиц до N, используя макрос в Rust

#rust #macros #rust-macros

#Ржавчина #макросы #rust-макросы

Вопрос:

Я хотел бы создать макрос для преобразования count!(5) в 1 1 1 1 1 . Последняя причина заключается в использовании count!(N) - 1 в определении структуры, где N const generics .

 macro_rules! count {
    (1) => {0};
    (2) => { 1   count!(1)};
    ($n: tt) => { 1   count!($n - 1)};
}

struct WindowIterator<I, Item, const N: usize> {
    iter: I,
    values: Box<[Item; count!(N) - 1 ]>,
    index: usize,
}

 

С этим определением я получаю ошибку типа no rules expected the token N.

Как я могу изменить свой код, чтобы сделать его правильным?

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

1. Ваш план не будет работать, поскольку макросы расширяются перед любым вычетом типа или созданием экземпляра шаблона; это и макросы работают только через токены, а не значения. Тем не менее, я озадачен этой идеей в первую очередь, поскольку ваш код компилируется с использованием N directly: playground .

2. Это похоже на проблему XY .

3. обновлены вопросы. Мне нужно создать массив длиной N-1

Ответ №1:

К сожалению, это невозможно с текущим стабильным Rust. Текущая поддержка Rust для const generics — MVP, а const generics со сложными выражениями не поддерживаются в стабильной сборке.

Однако это возможно с ночной сборкой Rust.

 #![feature(generic_const_exprs)]

struct WindowIterator<I, Item, const N: usize>
where
    [Item; N - 1]: Sized,
{
    iter: I,
    values: Box<[Item; N - 1]>,
    index: usize,
}
 

Ссылка на Rust Playground в приведенном выше примере

Вы можете ознакомиться с более подробной информацией в этом официальном сообщении в блоге Rust о const generics.

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

1. обновлены вопросы. Мне нужно создать массив длиной N-1

2. @allevo я обновил ответ.

Ответ №2:

Расширение макроса работает с необработанными токенами. Если вы будете следовать процессу расширения макроса…

 #![feature(log_syntax)]

macro_rules! count {
    (1) => {{
        log_syntax!(MatchOne count!(1));
        0
    }};
    (2) => {{
        log_syntax!(MatchTwo count!(2));
        0
    }};
    ($n:tt) => {{
        log_syntax!(MatchNumber count!($n));
        1   count!($n - 1)
    }};
    ($($n:tt)*) => {{
        log_syntax!(MatchAny count!($($n)*));
    }};
}
 

Игровая площадка.

Выводит:

 MatchNumber count! (5)
MatchAny count! (5 - 1)
 

Второй вызов не получил 4 , кроме 5 - 1 необработанных токенов.

То, что вы хотите, можно сделать с помощью процедурных макросов, например:

 extern crate proc_macro;

use itertools::Itertools; // When std's intersperse() will be stable, can get rid of this.
use proc_macro::{Delimiter, Group, Literal, Punct, Spacing, TokenStream, TokenTree};

#[proc_macro]
pub fn count(number: TokenStream) -> TokenStream {
    let number = number.to_string().parse().expect("expected a number"); // A simple way to parse int literal without `syn`
    let result = if number == 0 {
        TokenTree::Literal(Literal::u64_unsuffixed(0))
    } else {
        TokenTree::Group(Group::new(
            Delimiter::None,
            std::iter::repeat(TokenTree::Literal(Literal::u64_unsuffixed(1)))
                .take(number)
                .intersperse(TokenTree::Punct(Punct::new(' ', Spacing::Alone)))
                .collect(),
        ))
    };
    result.into()
}
 

Однако, это, вероятно, не то, что вы хотите.