Search code examples
rustrust-macros

match the very last argument of a Rust macro as a closure


Is there a way to adjust the macro below, in order to detect when the last argument of the macro call is a closure? The code below works, but it's using a semicolon to disambiguate, and that doesn't look right.

Could the closure argument be identified specifically at invocation, e.g. closure: || { ... }?

macro_rules! invoke_me {
    ($func_name:ident, $($arg:expr),*; || $closure:expr ) => {{
        println!("{}({})", stringify!($func_name), stringify!($($arg),*));
        println!("With closure");
        let result: Result<(),String> = $closure;
        result
    }};
    ($func_name:ident, $($arg:expr),*) => {{
        println!("{}({})", stringify!($func_name), stringify!($($arg),*));
        println!("No closure");
        Ok::<(),String>(())
    }};
}

fn main() {
    invoke_me!(foo, 1, 2, 3).unwrap();
    invoke_me!(foo, 1, 2, 3; || {
        println!("Ok from closure");
        Ok(())
    }).unwrap();
    let _ = invoke_me!(foo, 1, 2, 3; || {
        println!("Error from closure");
        Err("Error".to_string())
    });
}

Solution

  • Using the idea from How to separate last expression in repeated expressions in a Rust macro? (see ChrisB's comment), I got this to work:

    macro_rules! invoke_me {
        ($func_name:expr, $($rest:tt)+) => {
            invoke_me!(@transpose $func_name, [ ] $($rest)+)
        };
        (@transpose $func_name:expr, [ $($args:expr)*] || $closure:expr) => {{
            println!("{}({})", stringify!($func_name), stringify!($($args),*));
            println!("With closure");
            let result: Result<(),String> = $closure;
            result
        }};
        (@transpose $func_name:expr, [$($temp:expr)*] $arg:expr , $($rest:tt)+) => {
            invoke_me!(@transpose $func_name, [$($temp)* $arg] $($rest)+)
        };
        (@transpose $func_name:expr, [$($args:expr)*] $last_arg:expr) => {{
            println!("{}({})", stringify!($func_name), stringify!($($args),*, $last_arg));
            println!("No closure");
            Ok::<(),String>(())
        }}
    }
    
    fn main() {
        invoke_me!(foo, 1, 2, 3).unwrap();
        invoke_me!(foo, 1, 2, 3, || {
            println!("Ok from closure");
            Ok(())
        }).unwrap();
        let _ = invoke_me!(foo, 1, 2, 3, || {
            println!("Error from closure");
            Err("Error".to_string())
        });
    }
    

    playground

    Although this is possible, it would probably be a better idea to just keep the semicolon, as this is slow.