Search code examples
rustrust-macros

Conditionally generate a From impl based on field type


I'm trying to create a generic impl for generating a From/Into based on different field types.

Link to Playground

I'm seeing the following issues:

error[E0425]: cannot find value `item` in this scope
  --> src/lib.rs:23:21
   |
23 |             $param: item.$param,
   |                     ^^^^ not found in this scope
...
65 | create_impl! { TargetStruct, InputStruct, { field1: Option<String>, field2: Option<Uuid> }}
   | ------------------------------------------------------------------------------------------- in this macro invocation

Can anyone point me in the right direction on how to get this to work / if it's possible. At this point, I'm uncertain of how to pass the input parameter to the rules.

Thanks!


#[macro_use]
macro_rules! create_impl {
    ( @ $target:ident, $input:ident, { } -> ($($result:tt)*) ) => (
      impl From<$input> for $target {
          fn from(item: $input) -> Self {
            Self {
              $($result)*
              ..Default::default()
            }
        }
    });

    ( @ $target:ident, $input:ident, { $param:ident : Option<Uuid>, $($rest:tt)* } -> ($($result:tt)*) ) => (
        create_impl!(@ $target, $input, { $($rest)* } -> (
            $($result)*
            $param: item.$param.map(|v| v.to_string()),
        ));
    );

    ( @ $target:ident, $input:ident, { $param:ident : $type:ty, $($rest:tt)* } -> ($($result:tt)*) ) => (
        create_impl!(@ $target, $input, { $($rest)* } -> (
            $($result)*
            $param: item.$param,
        ));
    );


    ( @ $target:ident, $input:ident, { $param:ident : $type:ty, $($rest:tt)* } -> ($($result:tt)*) ) => (
        create_impl!(@ $target, $input, { $($rest)* } -> (
            $($result)*
            $param: item.$param,
        ));
    );

    ( $target:ident, $input:ident, { $( $param:ident : $type:ty ),* $(,)* } ) => (
        create_impl!(@ $target, $input, { $($param : $type,)* } -> ());
    );
}

Solution

  • You could let rust do the heavy lifting:

    macro_rules! memberize_result {
        ($item: ident, $param:ident : Option<Uuid>) => (
            $item.$param.map(|v| v.to_string())
        );
    
        ($item: ident, $param:ident : Option<String>) => (
            $item.$param
        );
    }
    
    #[macro_use]
    macro_rules! create_impl {
        ( $target:ident, $input:ident, { $( $param:ident : ($($type:tt)*) ),* $(,)* } ) => (
            impl From<$input> for $target {
                fn from(item: $input) -> Self {
                    Self {
                        $($param: memberize_result!(item, $param : $($type)*),)*
                        ..Default::default()
                    }
                }
            }
        );
    }
    
    
    use uuid::Uuid; // 0.8.1
    
    #[derive(Default)]
    pub struct InputStruct {
        pub field1: Option<String>,
        pub field2: Option<Uuid>,
    }
    
    #[derive(Default)]
    pub struct TargetStruct {
        pub field1: Option<String>,
        pub field2: Option<String>,
    }
    
    // Trying to turn this into a macro
    // impl From<ExampleStruct> for TargetStruct {
    //     fn from(item: ExampleStruct) -> Self {
    //         let mut res = Self::default();
    //         res.field1 = item.field1;
    //         res.field2 = item.field2.map(|v| v.to_string());
    //         res
    //     }
    // }
    
    create_impl! { TargetStruct, InputStruct, { field1: (Option<String>), field2: (Option<Uuid>) }}