How to define tuple type repeatedly with a dynamic size provided?

I wonder if w can write a macro to generate this tuple type (String, String, String) to something like a shorter form (String; 3) in Rust?.

Tuples are typically used for a mix of types, but for a repeated type, how about a [String; 3] array?

I expect it is possible to write a macro like tuple!(String; 3), but I'm not sure why you want that.

3 Likes

The reason is I use one method from a library (redis-rs) and the method requires me to define type in tuple so that I can view the return data from the method, I guess they base on the type to cast the value. In my case, this method is dynamic and sometimes it returns 2 sometimes 3.

if you only need two or three element homogenous tuples, a macro would be as easy as:

macro_rules! repeat {
    ($t:ty ; 2) => { ($t, $t) };
    ($t:ty ; 3) => { ($t, $t, $t) };
}

fn default<T: Default>() -> T {
    T::default()
}

fn main() {
    let t: repeat!(&str; 3) = default();
    
    assert_eq!(t, ("", "", ""));
}

Playground.

No, 2-3 is only an example. Are there any way to support this to 1..n elements?

You can do it with a recursive macro.

2 Likes

What a neat trick, very cool!

Also an idea would be a function-like procedural macro like this:

use proc_macro::TokenStream;

use syn::parse::{Parse, ParseStream};
use syn::{parse_macro_input, LitInt, Result, Token, Type};

use quote::quote;

struct Args {
    ty: Type,
    repeat: usize,
}

impl Parse for Args {
    fn parse(input: ParseStream) -> Result<Self> {
        let ty = input.parse()?;
        let _: Token![;] = input.parse()?;
        let repeat = input.parse::<LitInt>()?.base10_parse()?;

        Ok(Args { ty, repeat })
    }
}

#[proc_macro]
pub fn repeat(input: TokenStream) -> TokenStream {
    let args = parse_macro_input!(input as Args);

    let ty = args.ty;

    let mut res = quote! {};

    for _ in 0..args.repeat {
        res = quote! { #res #ty, };
    }

    quote! { ( #res ) }.into()
}
fn default<T: Default>() -> T {
    T::default()
}

#[test]
fn repeat3() {
    let t: repeat!(&str; 3) = default();

    assert_eq!(t, ("", "", ""));
}

#[test]
fn repeat10() {
    let t: repeat!(&str; 10) = default();

    assert_eq!(t, ("", "", "", "", "", "", "", "", "", ""));
}

Only works with literals though, something along the lines of:

const N: usize = 10;
let t: repeat(&str; N) = default();

won't work

1 Like

Are there anyway to pass dynamic N into this repeat macro? I'm thinking to pass a function into a macro and then invoke it to get the value.

No, macros are a purely compile-time (what's more, syntactic) abstraction. They don't even have access to type information, let alone run-time values. What you are trying to do doesn't make sense anyway – Rust is statically typed, so you can't create different, value-dependent types at runtime. (The closest thing would be an enum or a trait object, but that too would require explicitly listing a finite set of pre-determined types.)

3 Likes

Beware that the codegen/quoting part is accidentally quadratic! A more "proper" solution would be:

let iter = std::iter::repeat(args.ty).take(args.repeat);
quote!{ (#(#iter,)* }
2 Likes

Gotcha! Thanks everyone for helping me with my problem

You are completely right, I tent to forget that quote! also supports repetition like declarative macros :sweat_smile:.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.