0

I would like to create a macro for transforming count!(5) into 1+1+1+1+1. The final reason is to use count!(N) - 1 into a structure definition where N is a 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,
}

With this definition, I receive an error like no rules expected the token N .

How can I change my code for making it correct?

allevo
  • 844
  • 1
  • 9
  • 20
  • 4
    Your plan will not work since macros are expanded *before* any type deduction or template instantiation; that and macros only work via tokens, not values. However, I'm puzzled by the idea in the first place since your code compiles using `N` directly: [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=de997c1d1f41ab892f040795380878e5). – kmdreko Dec 04 '21 at 22:01
  • It seems like an [XY problem](https://xyproblem.info/). – Chayim Friedman Dec 05 '21 at 11:03
  • updated the questions. I need to create an array with length N-1 – allevo Dec 05 '21 at 11:07

2 Answers2

3

Sadly, it's not possible with current stable Rust. Current Rust support for const generics is MVP, and const generics with complex expressions are not supported in stable build.

However, it's possible with Rust nightly build.

#![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

You can check out further details in this official Rust blog post about const generics.

Hyeon Kim
  • 111
  • 5
0

Macro expansion works on raw tokens. If you follow the macro expansion process...

#![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.

Outputs:

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

The second invocation didn't get 4, but 5 - 1, the raw tokens.

What you want can be done with procedural macros, for example:

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

However, this is probably not what you want.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77