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?

3
  • 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. Commented Dec 4, 2021 at 22:01
  • It seems like an XY problem. Commented Dec 5, 2021 at 11:03
  • updated the questions. I need to create an array with length N-1 Commented Dec 5, 2021 at 11:07

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

updated the questions. I need to create an array with length N-1
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.