6

I've created a Rust macro which expands to a function declaration.

macro_rules! build_fn
{
    ($name:tt) => {

        pub fn $name(&self) -> Result<i32, Box<dyn Error>>
        {
            // <implementation>
            
            Ok(0)
        }
    };
}

Is it possible to expand this so the macro can take variable parameters?

e.g.

($name:tt, /*$variable_args ? */) => {

        pub fn $name(&self, /*$variable_args ? */) -> Result<i32, Box<dyn Error>>
        {
            // ...
            
            Ok(0)
        }
    };
}

Playground

IanS
  • 151
  • 2
  • 8
  • [Repetition](https://doc.rust-lang.org/rust-by-example/macros/repeat.html)? – Chayim Friedman Jan 27 '22 at 01:00
  • Yes, I think I need something like this `$($arg:expr),*` .. but I can't quite get the syntax right. It makes me question if it's possible in this context but I confess that macros are certainly my weak point with Rust. – IanS Jan 27 '22 at 01:14
  • 3
    `$($arg:expr),*` covers expressions, you need names and types for the arguments. You can use something like `$($p:pat : $t:ty),* $(,)?` for full cover, but if that's at the end, better to use `$($args:tt)*`. – Chayim Friedman Jan 27 '22 at 01:18
  • I've made _some_ progress with this but it's still not right for multiple args. [Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=d798cc6a744b53af47dd8cf40fc3b115) – IanS Jan 31 '22 at 22:23

2 Answers2

4

Indeed, it's possible. You need to expand the parameters as $field: $ty:

use std::error::Error;

macro_rules! build_fn
{
    ($name:tt, $($v:ident: $t:ty),*) => {

        pub fn $name(&self, $($v: $t),*)
        {
            let args = [$($v,)*];
            println!("Args = {:?}", args);
        }
    };
}

struct MyStruct {}

impl MyStruct {
    build_fn!(test_single_arg, x: i32);
    build_fn!(test_multi_arg, x: i32, y: i32);
}

fn main() -> Result<(), Box<dyn Error>> {
    let my_struct = MyStruct {};
    
    my_struct.test_single_arg(10);
    my_struct.test_multi_arg(1, 2);

    Ok(())
}

Link to the playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=d798cc6a744b53af47dd8cf40fc3b115

ranfdev
  • 63
  • 5
  • 1
    This looked promising, but it doesn't currently compile on the playground. Would you mind updating your answer? – zertyz Aug 18 '22 at 20:28
3

Since @ranfdev's answer is not currently compiling, I've fixed & simpified & extended it to support multiple types -- and it now runs in Rust stable:

use std::fmt::Write;

macro_rules! build_vararg_fn {
    ($name:tt, $($v:tt: $t:ty),+) => {

        fn $name($($v: $t),+) {
            let mut msg = String::from("args: ");
            $(
                write!(msg, "{:?}, ", $v).unwrap();
            )+
            println!("{}", &msg[..msg.len()-2]);
        }
    }
}

fn main() {
    build_vararg_fn!(test_single_arg, x: i32);
    build_vararg_fn!(test_multi_arg, x: i32, y: i32);
    build_vararg_fn!(test_multi_type, x: i32, y: i32, z: f64);

    test_single_arg(10);
    test_multi_arg(1, 2);
    test_multi_type(1, 2, 3.14159);
}

Output:

args: 10
args: 1, 2
args: 1, 2, 3.14159

See it on Playgroud!

zertyz
  • 601
  • 6
  • 9