How to pass a type constructor to a declarative macro?

I'm new to macros, and have a one to which I need to pass a type constructor. This variant works ok:

macro_rules! derive_db_conversions_adl_1 {
    ($decl:ident) => {
        impl<P1: serde::Serialize> crate::db::types::DbConversions 
            for $decl<P1>
        {
...
        }
    };
}

but taking an :ident, only accepts an unscoped name. I need to pass a scoped name. Unfortunately using :path in lieu of :ident does not compile. ie this:

macro_rules! derive_db_conversions_adl_1 {
    ($decl:path) => {
        impl<P1: serde::Serialize> crate::db::types::DbConversions
             for $decl<P1>
        {
...
        }
    };
}

gives the error:

    |            for $decl<P1>
    |                     ^ expected one of `!`, `+`, `where`, or `{`
    |

How can this be done?

Thanks!

There is no stock solution to do this, so if you stick to macros you'll need either a "tt muncher" or a proc macro, though both are elaborate.

Or else the HKT emulation pattern as recently described by @quinedot might be applicable.

You can take a list of ::-separated idents Rust Playground

macro_rules! derive_db_conversions_adl_1 {
    ($($decl:ident)::+) => {
        impl<P1: serde::Serialize> crate::db::types::DbConversions
            for $($decl)::+<P1>
        {
        }
    };
}

This definitely has problems if you get creative, like you can't do <A as Trait>::Type, but for an internal macro it would be fine.

Edit: or you can use the argument and rename it.

macro_rules! derive_db_conversions_adl_1 {
    ($decl:path) => {
        use $decl as Type;
        impl<P1: serde::Serialize> crate::db::types::DbConversions
            for Type<P1>
        {
        }
    };
}

(this needs to be scoped if you want to use the macro multiple times: Rust Playground)

2 Likes

if your macro only expands to an impl block, a common workaround is to use an alias:

macro_rules! derive_db_conversions_adl_1 {
    ($decl:path) => {
        // create an anonymous scope
        const _: () = {
            // create an local aliased import
            use $decl as Decl;
            impl<P1: serde::Serialize> crate::db::types::DbConversions 
                for Decl<P1>
            {
            }
        };
    };
}

Thanks all - I went with the :: -separated idents approach.