0

I want to write a macro, which generates a struct with a default fn new(...) function. If the desired struct has a String field, I want new to be a template function like fn new<S: Into<String>>(..., field: S,...).

The problem occurred when creating these optional template parameter.


For example, for

impl_type!(
    User {
        id: i64,
        name: String,
    }
}

I want to get:

pub struct User { /* fields */ }

impl User {
    pub fn new<S: Into<String>>(id: i64, name: S) -> Self {
        User {
            id: id,
            name: name.into(),
        }
    }
}

What I'm getting:

error: expected one of `(` or `<`, found `impl_type`
  --> src/macros/impl_type.rs:39:24
   |
38 |           impl $name {
   |  ____________________-
   | |____________________|
   | |
39 | |             pub fn new impl_type!(@template_if_string $($field_type,)*)
   | |                        ^^^^^^^^^ expected one of `(` or `<`
40 | |             ($($field: impl_type!(@ty_fn $field_type),)*) -> Self {

My macro_rules! code:

macro_rules! impl_type {
    (
        $name:ident {
            $($field:ident : $field_type:ty,)*
        }
    ) => {
        pub struct $name {
            $($field: $field_type,)*
        }

        impl $name {
            pub fn new impl_type!(@template_if_string $($field_type,)*)
            ($($field: impl_type!(@ty_fn $field_type),)*) -> Self {
                $name {
                    $($field: ($field).into(),)* 
                }
            }
        }
    };

    (@ty_fn String) => ( S );
    (@ty_fn $field_type:ty) => ( impl_type!($field_type) );

    (@template_if_string) => {};
    (@template_if_string String, $($field_type:ty,)*) => {
        <S: std::convert::Into<String>>
    };
    (@template_if_string $field_type:ty, $($other_field_type:ty,)*) => {
        impl_type!(@template_if_string $($other_field_type,)*)
    };
}

How could I invoke impl_type! after new or refactor the code so it works as intended?

I'm new to Rust. Any help is appreciated, thanks for your patience.

rrr
  • 1
  • 2

1 Answers1

1

What you are trying to do doesn't work with declarative macros, because each declarative macro must return a valid node of the syntax tree. For instance, you couldn't have a declarative macro just insert the token { where it was invoked.

Similarly, just the arguments of a function declaration, with the parenthesis but without the name, is not a node of the syntax tree. I'm not even sure if it's possible to achieve what you are trying to do with declarative macros, but in any case it's a problem much better suited for procedural macros:

  • procedural macros have a more lightweight syntax in the used code (#[something]... instead of something! { ... });
  • procedural macros are much broader, that is, even if you achieved to do what you are trying to do with declarative macros, it would most likely be a nightmare to also support generics;
  • procedural macros given you access to libraries that already implement parsing and traversing standard struct definitions.
jthulhu
  • 7,223
  • 2
  • 16
  • 33