Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
355 views
in Technique[技术] by (71.8m points)

rust - Difficulty with Recursive Macro

I'm trying to remove code duplication in this Rust code using a macro:

enum AnyError { Error(Error), ParseIntError(ParseIntError), }
impl From<Error> for AnyError {
    fn from(err: Error) -> AnyError { AnyError::Error(err) }
}
impl From<ParseIntError> for AnyError {
    fn from(err: ParseIntError) -> AnyError { AnyError::ParseIntError(err) }
}

This is the macro code I'm trying to get working, which I believe should generate the above:

enum AnyError {
    Error(Error),
    ParseIntError(ParseIntError),
}

macro_rules! any_error {
    ($n: ident) => ();
    ($n: ident, $x:ident, $($y:ident),*) => {
        impl From<$x> for $n {
            fn from(err: $x) -> $n {
                $n::$x(err)
            }
        }
        any_error!($n, $($y),*);
    };
}

any_error!(AnyError, Error, ParseIntError);

This is the error I'm getting from the compiler:

error: unexpected end of macro invocation
  --> src/main.rs:17:28
   |
9  | macro_rules! any_error {
   | ---------------------- when calling this macro
...
17 |         any_error!($n, $($y),*);
   |                            ^ missing tokens in macro arguments

I've tried a bunch of different variants of this. If I remove the recursive call to any_error! then it successfully generates one of the From impls, so that part seems fine. Does anyone know what's wrong, or what the compiler error actually means?

question from:https://stackoverflow.com/questions/65557583/difficulty-with-recursive-macro

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The problem is that your macro handles zero variants, and two or more variants, but fails when there is exactly one:

any_error!(AnyError, Error);  // None of the cases handle this!

A possible fix is to move the , into the $y matcher, so that in the exactly one case, the macro doesn't expect a trailing comma:

macro_rules! any_error {
    ($n: ident) => ();
    ($n: ident, $x:ident $(, $y:ident)*) => {
        impl From<$x> for $n {
            fn from(err: $x) -> $n {
                $n::$x(err)
            }
        }
        any_error!($n $(, $y)*);
    };
}

Alternatively, you can put the impl inside the $()* block and skip the recursion altogether:

macro_rules! any_error {
    ($n: ident) => ();
    ($n: ident, $($x:ident),*) => {
        $(
            impl From<$x> for $n {
                fn from(err: $x) -> $n {
                    $n::$x(err)
                }
            }
        )*
    };
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...