How to import a trait defined at another file inside a macro?

Hi, everyone!

Now I try to implement a macro rule that will use a trait defined at another file. I can import the trait explicitly when I use this macro. But this way couldn't make sure that I don't forget to import the trait. Although the compiler would complain to me.

So I want to know how to import a trait automatically when using the macro rule?

Here is my macro:

// filename: procedure.rs
#[macro_export]
macro_rules! make_proc {
    ( $name:literal, |_| { $exps:expr }) => {
        Procedure::new($name, 0, |_| $exps)
    };
    ( $name:literal, $num:literal, |$($arg_name:ident:$arg_type:ty),+| { $exps:expr } ) => {
        Procedure::new($name, $num, |args| {
            let mut idx = 0usize;
            $(
                let $arg_name = <$arg_type>::try_from(&args[idx]).unwrap();
                #[allow(unused_assignments)]
                {
                    idx += 1;
                }
            )+
            $exps
        })
    };
}

And the try_from definition is at another file value.rs beside procedure.rs.

Here is the TryFromValue trait:

// filename: value.rs
pub trait TryFromValue: Sized {
    fn try_from(v: &Value) -> Result<Self, TypeError>;
}

More details at here and here if you need.

In this particular case, I'd probably use the fully-qualified syntax to use the trait without importing it:

let $arg_name = <$arg_type as ::std::convert::TryFrom<_>>::try_from(&args[idx]).unwrap();

Alternatively, you can import the trait inside the code generated by the macro:

    ( $name:literal, $num:literal, |$($arg_name:ident:$arg_type:ty),+| { $exps:expr } ) => {
        Procedure::new($name, $num, |args| {
            use ::std::convert::TryFrom;            // <-- HERE
            let mut idx = 0usize;
            $(
                let $arg_name = <$arg_type>::try_from(&args[idx]).unwrap();
                #[allow(unused_assignments)]
                {
                    idx += 1;
                }
            )+
            $exps
        })

Oh, I should clarify that the try_from isn't come from the std::convert::TryFrom trait. It comes from my custom trait named TryFromValue in the value.rs file. And here is it:

// filename: value.rs
pub trait TryFromValue: Sized {
    fn try_from(v: &Value) -> Result<Self, TypeError>;
}

Both value.rs and procedure.rs inside the same crate, the structure below:

reg_machine
    |- machine
        |- value.rs
        |- procedures.rs
    |- lib.rs

Here are my attempts:

// first one: absolute path, not work
    ( $name:literal, $num:literal, |$($arg_name:ident:$arg_type:ty),+| { $exps:expr } ) => {
        Procedure::new($name, $num, |args| {
            let mut idx = 0usize;
            $(
                let $arg_name: <$arg_type> = reg_machine::machine::value::TryFromValue::try_from(&args[idx]).unwrap();
                                          // ^^^^^^^^^^^ here
                #[allow(unused_assignments)]
                {
                    idx += 1;
                }
            )+
            $exps
        })
// second one: relative path, not work too
    ( $name:literal, $num:literal, |$($arg_name:ident:$arg_type:ty),+| { $exps:expr } ) => {
        Procedure::new($name, $num, |args| {
            let mut idx = 0usize;
            $(
                let $arg_name: <$arg_type> = crate::machine::value::TryFromValue::try_from(&args[idx]).unwrap();
                                          // ^^^^^ here
                #[allow(unused_assignments)]
                {
                    idx += 1;
                }
            )+
            $exps
        })

It looks like the key thing you're missing is $crate which is always the root of the crate where the macro is defined, regardless of where it's used-- You should refer to the trait as $crate::machine::value::TryFromValue when inside the macro.

2 Likes

Wow! Thanks for your help sincerely. :grin:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.