Macro_rules shared by normal and unsafe code

I have a macro that expands to a function definition, and works fine for various cases. But some expansions contain unsafe code and some don't. Like this contrived example:

extern crate core;
use core::ptr;

struct Stuff(i32);
fn extract_boring(s: &Stuff) -> i32 {  s.0 }
unsafe fn extract_daring(s: &Stuff) -> i32 { ptr::read(s).0 }

macro_rules! doubler {
    ($name: ident @ $extract: ident) => {
        unsafe fn $name(stuff: &Stuff) -> i32 {
            $extract(stuff) + $extract(stuff)
        }
    };
} 
doubler!(boring @ extract_boring);
doubler!(daring @ extract_daring);

fn main() {
    let s1 = Stuff(12);
    let s2 = Stuff(21);
    unsafe {
        println!("{}", boring(&s1));
        println!("{}", daring(&s2));
    }
}

This compiles and works fine, but some safe code has to be marked unsafe. What can I do?

  • Write two macros with the same body. But I really want to avoid duplicating the body (which is a lot more complicated in reality), that's why I'm using a macro in the first place.
  • Make the unsafe keyword conditional in the macro expansion. But the list of fragment specifiers I found including visibility qualifiers doesn't seem to cover that.
  • Limit the macro to the function body as an expression. But the actual body has a somewhat complicated control flow with return statements. I can replace these with labeled break statements in a fake loop, but it just looks like do-not-repeat-yourself taken a step too far.

Am I missing something?

1 Like

This is, imho, the way to go:

macro_rules! doubler {
    (
        @safety
            [$($unsafe:tt)?]
        @rest
            $name:ident := 2 * $extract:ident
    ) => (
        $($unsafe)?
        fn $name (stuff: &'_ Stuff) -> i32
        {
            $extract(stuff) + $extract(stuff)
        }
    );

    (
        unsafe $($rest:tt)*
    ) => (
        doubler! {
            @safety
                [unsafe]
            @rest
                $($rest)*
        }
    );

    (
        $($rest:tt)*
    ) => (
        doubler! {
            @safety []
            @rest $($rest)*
        }
    );
} 

doubler! {
    boring := 2 * extract_boring
}
doubler! {
    unsafe daring := 2 * extract_daring
}
2 Likes

I had a lot of trouble understanding what's going on, but it opened a world to me. I slimmed it down to:

macro_rules! doubler {
    (
        $extract:ident >> $($decl:tt)+
    ) => (
        $($decl)+ (stuff: &'_ Stuff) -> i32 {
            $extract(stuff) + $extract(stuff)
        }
    );
}

doubler! {
    extract_boring >> fn boring
}
doubler! {
    extract_daring >> unsafe fn daring
}

Playground

PS the >> idelimiter s optional here (and you might prefer a delimiter that doesn't confuse syntax highlighting) but the crux is that the variable list of tokens is at the back. And that a single token is a "token tree" too.

2 Likes

Oh yes, the only way to program more complex "logic" with macro_rules! macro is by (ab)using recursion with the different rules. See the little book of Rust macros.