Writing a `macro_rules` macro that takes a block *body*

It's easy to write a MBE that takes a block.

Is there a nice way to write a MBE that takes a block body instead -- some number of statements followed by an optional trailing expression?

I tried this:

macro_rules! iife {
    ( $($s:stmt;)* $($e:expr)? ) => {
        (||{
            $($s;)*
            $($e)?
        })()
    }
}

fn main() {
    let x = iife! {
        let x = 4;
        x + 3
    };
}

But it fails with a "local ambiguity" error: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1fd198af9cc73090869c680d856f3c4c

I can always just accept arbitrary tokens but that feels less nice, and I assume it'll rustfmt worse and such.

This seems to work:

macro_rules! iife {
    ( $($s:stmt);* ) => {
        (||{
            $($s)*
        })()
    }
}

fn main() {
    let x = iife! {
        let x = 4;
        x + 3
    };
    println!("{}", x); // prints 7
}

Playground

Edit: Hmm, that does seem to require it ends in an expression. Not sure how to fix it yet

That one, interestingly, also seems to allow this:

    let x = iife! {
        let x = 4;
        let y = 3
    };

Which seems less than ideal.

uh oh, that's a problem

Well, I've got this:

macro_rules! iife {
    ( $($s:stmt;)* ) => {
        (||{
            $($s)*;
        })()
    };
    ( $($s:stmt;)* $e:expr ) => {
        (||{
            $($s)*;
            $e
        })()
    };
}

fn main() {
    let x = iife! {
        let x = 4;
        x + 3;
    };
    println!("{:?}", x);
}

but it's producing a rather strange error:

error[E0308]: mismatched types
  --> src/main.rs:18:9
   |
18 |         x + 3;
   |         ^^^^^- help: consider using a semicolon here
   |         |
   |         expected `()`, found integer

Curiously, expanding the macro gives (|| { let x = 4; x + 3; ; })();, which works perfectly fine if copy-pasted in the macro's place.

It doesn't - the expanded code returns (), not 7.

Oh, I did realize that, I just meant it doesn't fail to compile.

Sorry, copied the wrong code. Updated it. too many playground tabs open :sweat_smile:

$($s:stmt;)* is problematic by itself because the semicolon is optional after certain statements. This is a legal function, for example, but its body won't match:

fn f(x:bool)->bool {
    if x { return true; }
    else { return false; }
    unreachable!();
}
3 Likes
// #![allow(unused)]

macro_rules! iife {
    // There seems `;` is no longer recognized, so no way to remove `;`
    // and thus redundant_semicolons warnings are triggered.
    // (@inner ;) => { {print!(";");} };
    (@inner $s:stmt) => { $s };
    ( $($s:stmt)+ ) => {
        (||{
            $(iife!{@inner $s})+
        })()
    }
}

fn main() {
    let x = iife! {
        let x = 4;
        x + 3
    };
    let y = iife! {
        let x = 2;
        if x > 0 { return 100 }
        x - 1
    };
    dbg!((x, y));
}

playground

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.