Search code examples
rustsubstitutionrust-macros

Using macros so that you don't have to write down a long parameter list every time


Let's say I have a large function A. As a means of organisation, I have split it into multiple sub-functions. All of these rely on a standard couple of variables initialised and provided by A. Is there a way to use macros so as not to write all of these argument lists and parameter lists out every time I call and define one such sub-function?

For example: substitute this:

fn A(){
    let mut mainDeck = Vec<Card>;
    let mut dealer = Participant::new();
    let mut players : Vec<Participant> = Vec::with_capacity(4);
    let extraArgument = 21;

    shuffleDeckAndDealFirstCards(&mut mainDeck, &mut dealer, &mut players);
    playGame(&mut mainDeck, &mut dealer, &mut players, extraArgument);
    evaluateWinner(&mut mainDeck, &mut dealer, &mut players)
    payout(&mut mainDeck, &mut dealer, &mut players); }

fn shuffleDeckAndDealFirstCards(
    mainDeck : &mut Vec<Card>,
    dealer : &mut Participant,
    players : &mut Vec<Participant> ){
    /* ... */ }

fn playGame(
    mainDeck : &mut Vec<Card>,
    dealer : &mut Participant,
    players : &mut Vec<Participant>,
    extraArgument : i32 ){
    /* ... */ }

fn evaluateWinner(
    mainDeck : &mut Vec<Card>,
    dealer : &mut Participant,
    players : &mut Vec<Participant> ){
    /* ... */ }

fn payout(
    mainDeck : &mut Vec<Card>,
    dealer : &mut Participant,
    players : &mut Vec<Participant> ){
    /* ... */ }

for this:

fn A(){
    let mut mainDeck = Vec<Card>;
    let mut dealer = Participant::new();
    let mut players : Vec<Participant> = Vec::with_capacity(4);
    let extraArgument = 21;

    shuffleDeckAndDealFirstCards(defaultArgs!());
    playGame(defaultArgs!(), extraArgument);
    evaluateWinner(defaultArgs!())
    payout(defaultArgs!());
}

fn shuffleDeckAndDealFirstCards(defaultParams!()){
    /* ... */
}

fn playGame(defaultParams!(), extraArgument : i32){
    /* ... */
}

fn evaluateWinner(defaultParams!()){
    /* ... */
}

fn payout(defaultParams!()){
    /* ... */
}

I tried using a simple macro that looks like:

macro_rules! defaultParams {
    () => {
        mainDeck : &mut deck_t,
        dealer : &mut dealer,
        players : &Vec<Participant>
    };
}

But when I try it on a function:

fn shuffleDeckAndDealFirstCards(defaultParams!()){
    /* ... */
}

It gives the error: expected one of '(' or '<', found 'defaultParams'

PS: I know that in this particular example the code looks quite clean even without the macros. But in reality, the code I'm working with is a bit more complex and will look like a mess if the parameter/argument list is written down completely each time. Also the sub-functions will have sub-sub-functions themselves which also requires the parameter list.


Solution

  • Macros can only expand to whole items. So, no, you can't.

    But even if you'll wrap the whole function with the macro, like suggested in the comment, you'll stuck with hygiene. Macro-generated identifiers cannot be used in your code. For example:

    // This macro does not accept all valid Rust fn declarations
    macro_rules! with_default_params {
        {
            $(
                fn $name:ident( $( $arg_name:ident : $arg_type:ty ),* $(,)? )
                    $( -> $ret_type:ty )?
                $body:block
            )*
        } => {
            $(
                fn $name(
                    main_deck: &mut Deck,
                    dealer: &mut Dealer,
                    players: &Vec<Participant>,
                    $( $arg_name : $arg_type ),*
                ) $( -> $ret_type )?
                $body
            )*
        };
    }
    
    with_default_params! {
    
    fn shuffle_deck_and_deal_first_cards() {
        let _ = main_deck; // Error
    }
    
    fn play_game(extra_argument: i32) {
        /* ... */
    }
    
    }
    

    Playground.

    What you can do is collect them to a shared place, but I'm almost certain this will require tt-munching. You can do that, but don't.

    The correct thing to do is definitely to collect them to a struct, and perhaps make the fns member functions too.

    Edit: For completeness sake, here's a tt-muncher that does what you want.