A brute-force approach for applying to structs and enums

One way we can solve our problems with writing a derive_deftly(Diff) template is a brute-force approach. Here we just use conditionals to write different versions of our template for the struct case and the enum case. To handle the difference between tuple and unit structs and variants, we can use conditionals as well.

Here are the new conditions we'll need:

  • is_enum - True if the top-level type is an enum.
  • is_struct - True if the top-level type is a struct.
  • v_is_unit - True if the current variant (or the top-level type, in the case of a struct) is a unit struct/variant.
  • v_is_tuple - True if the current variant (or the top-level type, in the case of a struct) is a tuple struct/variant.
  • v_is_named - True if the current variant (or the top-level type, in the case of a struct) is a struct/variant with named fields.

With these conditions, we can write our template, albeit with quite a lot of redundancy.

Note 1: You may want to refresh your memory about ${select1} and $<pasting>.

Note 2: We're ignoring generics in this example, for clarity.

#![allow(unused)]
fn main() {
pub trait Diff { type Difference: std::fmt::Debug; }
impl Diff for u32 { type Difference = (); }
use derive_deftly::{define_derive_deftly, Deftly};
define_derive_deftly!{
    Diff:

    ${define DIFF_TYPE
        { Option< <$ftype as Diff>:: Difference> }
    }

    ${select1 is_struct {
        ${select1 v_is_unit {
            #[derive(Debug)]
            $tvis struct $<$tname Diff>;
        } else if v_is_tuple {
            #[derive(Debug)]
            $tvis struct $<$tname Diff>(
                ${for fields {$DIFF_TYPE , }}
            );
        } else if v_is_named {
            #[derive(Debug)]
            $tvis struct $<$tname Diff> {
                $(
                    $fvis $fname: $DIFF_TYPE,
                )
            }
        }} // end select1
    } else if is_enum {
        #[derive(Debug)]
        $tvis enum $<$tname Diff> {
            $(
                ${select1 v_is_unit {
                    // nothing to do; we don't emit anything here.
                } else if v_is_tuple {
                    $<Both $vname>(
                        ${for fields {$DIFF_TYPE , }}
                    ),
                } else if v_is_named {
                    $<Both $vname> {
                        $(
                            $fname: $DIFF_TYPE,
                        )
                    },
                }} // end select1
            ) // end iteration over variants

            TypeChanged {
                original_value: $ttype,
                new_value: $ttype,
            },
        }
    }} // end select1
    impl Diff for $tname {
        type Difference = $<$tname Diff>;

        // ... and at this point, you still have to define
        // an implementation for `fn diff()`: good luck!
    }
}
use derive_deftly_template_Diff;
#[derive(Clone,Debug,Deftly)]
#[derive_deftly(Diff)]
struct Unit;
#[derive(Clone,Debug,Deftly)]
#[derive_deftly(Diff)]
struct Tuple(u32, u32);
#[derive(Clone,Debug,Deftly)]
#[derive_deftly(Diff)]
struct Named { a: u32, b: u32 }
#[derive(Clone,Debug,Deftly)]
#[derive_deftly(Diff)]
enum Enum { Un, Tup(u32,u32), Nam { a: u32, b: u32 } }
}

This is a viable approach, but not a very maintanable one: look how we've had to copy out our definition separately for every possible Rust syntax! We managed to save some repetition with $define, but we can still do better.