简体   繁体   中英

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

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.

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 }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM