在 Rust 中使用宏创建总和直到 N

Create sum of ones till N using macro in Rust

我想创建一个宏来将 count!(5) 转换为 1+1+1+1+1。 最后一个原因是在结构定义中使用 count!(N) - 1,其中 N 是常量泛型。

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 .

的错误

如何更改我的代码以使其正确?

遗憾的是,目前的 stable Rust 是不可能的。当前 Rust 对 const 泛型的支持是 MVP,在稳定版本中不支持具有复杂表达式的 const 泛型。

但是,使用 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 link of the example above

您可以在此 official Rust blog post about const generics 中查看更多详细信息。

宏扩展适用于原始标记。如果按照宏展开过程...

#![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)*));
    }};
}

Playground.

输出:

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

然而,这可能不是你想要的。