Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I generalize a Rust macro over different types of functions?

Tags:

macros

rust

I have a macro that takes a list of function declarations and turns them into different declarations.

macro_rules! re_export {
    ($(pub fn $i:ident($($arg:ident: $argty:ty)*) -> $ret:ty;)*) => ($(
        extern {
            pub fn $i($($arg: $argty),*) -> $ret;
        }
    )*);
    ($(pub fn $i:ident($($arg:ident: $argty:ty)*);)*) => ($(
        extern {
            pub fn $i($($arg: $argty),*);
        }
    )*);
}

Which is used like this:

re_export! {
    pub fn abs(i: c_int) -> c_int;
    pub fn rand() -> c_int;
    pub fn foo();
    pub fn add(i: c_int, j: c_int) -> c_int;
}

How can I generalize the macro so that I can give it multiple functions with or without args and return types and have it work on all of them. It's easy to make a macro that works on several functions of the same type, but I can't figure out how to make it work for different types.

like image 382
Mastax Avatar asked Feb 23 '16 23:02

Mastax


People also ask

What is the difference between macro and function Rust?

Another important difference between macros and functions is that you must define macros or bring them into scope before you call them in a file, as opposed to functions you can define anywhere and call anywhere.

Is macro_rules a macro?

macro_rules allows users to define syntax extension in a declarative way. We call such extensions "macros by example" or simply "macros". Each macro by example has a name, and one or more rules.

What is the point of macros Rust?

Rust has excellent support for macros. Macros enable you to write code that writes other code, which is known as metaprogramming. Macros provide functionality similar to functions but without the runtime cost. There is some compile-time cost, however, since macros are expanded during compile time.

Can a macro return a value Rust?

No, macros operate on syntax only.


1 Answers

Well, there are two ways.

If you want to parse this exact syntax, then you'll need to use a muncher. So, something like:

macro_rules! re_export {
    () => {};

    (
        pub fn $i:ident($($arg:ident: $argty:ty)*) -> $ret:ty;
        $($tail:tt)*
    ) => {
        extern {
            pub fn $i($($arg: $argty),*) -> $ret;
        }
        re_export! { $($tail)* } 
    };

    (
        pub fn $i:ident($($arg:ident: $argty:ty)*);
        $($tail:tt)*
    ) => {
        extern {
            pub fn $i($($arg: $argty),*);
        }
        re_export! { $($tail)* }
    };
}

This involves breaking off one function signature at a time, processing them recursively. This is the most flexible way of parsing things, but does mean that you can run up against the macro recursion limit. The default limit is 64, so if you have more input than that, you'll need multiple top-level macro invocations, or you'll have to manually raise the recursion limit by adding a #![recursion_limit="128"] attribute to your crate.

The other is to change the syntax so that you split then process the signatures in two steps. To do this, you must have some kind of regular top-level syntax for the signatures. For example:

macro_rules! re_export {
    ($({$($sigs:tt)*})*) => {
        $(
            re_export! { @fn $($sigs)* }
        )*
    };

    (@fn pub fn $i:ident($($arg:ident: $argty:ty),*) -> $ret:ty) => {
        extern {
            pub fn $i($($arg: $argty),*) -> $ret;
        }
    };

    (@fn pub fn $i:ident($($arg:ident: $argty:ty),*)) => {
        extern {
            pub fn $i($($arg: $argty),*);
        }
    };
}

Here, we wrap each function signature in {...}s. This is because matcher groups ((...), [...], and {...}) allow macro_rules! to match their contents blindly, without having to understand them. This allows us to match the irregular function signatures in a regular fashion. The top-level expansion simply forwards each individual function signature back to itself for actual processing. The @fn is just an internal rule marker to make sure we select the correct rule during recursion.

This doesn't have the same recursion limits that the previous one does... but requires you to use a slightly obtuse syntax:

re_export! {
    { pub fn abs(i: c_int) -> c_int }
    { pub fn rand() -> c_int }
    { pub fn foo() }
    { pub fn add(i: c_int, j: c_int) -> c_int }
}
like image 136
DK. Avatar answered Nov 16 '22 03:11

DK.