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.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.