Search code examples
rustmacrosconst-generics

How to create a proc macro that can read a const generic?


I wanted a way to create an array with non-copy values. So I came up with the following:

use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream, Result};
use syn::{parse_macro_input, Expr, LitInt, Token};

struct ArrayLit(Expr, LitInt);

impl Parse for ArrayLit {
    fn parse(input: ParseStream) -> Result<Self> {
        let v: Expr = input.parse()?;
        let _ = input.parse::<Token![;]>()?;
        let n: LitInt = input.parse()?;
        Ok(ArrayLit(v, n))
    }
}

#[proc_macro]
pub fn arr(input: TokenStream) -> TokenStream {
    let arr = parse_macro_input!(input as ArrayLit);
    let items = std::iter::repeat(arr.0).take(
        arr.1.base10_parse::<usize>().expect("error parsing array length"),
    );
    (quote! { [#(#items),*] }).into()
}

This works with numeric literal sizes, like:

fn f() -> [Option<u32>; 10] { 
    let mut it = 0..5;
    arr![it.next(); 10]
}

How do I change this proc-macro code so that it will take a const generic? For example, I'd like it to work with the following function:

fn f<const N: usize>() -> [Option<u32>; N] {
    let mut it = 0..5;
    arr![it.next(); N]
}

Solution

  • This is not possible as written. Your macro works by repeating a fragment of code, which necessarily happens before the program is parsed. Generics, including const generics are monomorphized (converted into code with, in this case, a specific value for N) well after the program has been parsed, type-checked, and so on.

    You will have to use a different strategy. For example, you could have your macro generate a loop which loops N times.

    By the way, if you're not already aware of it, check out std::array::from_fn() which allows constructing an array from repeatedly calling a function (so, more or less the same effect as your macro).