Example 3: Tracking the difference between objects
So far we've seen some of derive-deftly
's features
for defining different kinds of templates.
In this section, we'll extend on what we've learned already to build a template to track the difference between objects. Unlike our previous examples, this template will define a new "difference" type that mirrors the structure of the type we're applying it to.
Here's our motivating example.
Let's suppose we have some complex structures
and we want to expose the differences between those structures
in a human-readable way.
For example, we might want to write an assert_eq!
test
that shows only the actual changed fields between two instances,
rather than showing their entire Debug outputs.
This example is inspired by the
comparable
crate, which has a bunch of other useful features we won't be replicating here. If this code seems like something you'd want, you should probably usecomparable
instead.
To begin with,
let's assume our crate has defined a Diff
trait,
looking something like:
#![allow(unused)] fn main() { pub trait Diff { /// A type that describes the difference between /// two values of this type. type Difference: std::fmt::Debug; fn diff(&self, other: &Self) -> Option<Self::Difference>; } macro_rules! assert_no_difference { { $a:expr, $b:expr } => { match $a.diff($b) { Some(diff) => panic!("{} != {}: {:?}", stringify!($a), stringify!($b), diff), None => {}, } } } }
We'll also assume that we've implemented our Diff
trait
on a wide array of types from std
.
What kind of behavior do we want here?
When we write a template to derive Diff
on a struct MyStruct
,
we'll want our template to define a new struct whose fields represent
the changes between the old structure and the new.
#![allow(unused)] fn main() { pub trait Diff { type Difference: std::fmt::Debug; } impl Diff for String { type Difference = (); } struct SomeOtherStruct; impl Diff for SomeOtherStruct { type Difference = (); } // Example struct struct MyStruct { a: String, pub b: SomeOtherStruct } // Expected output #[derive(Debug)] struct MyStructDiff { a: Option< <String as Diff>::Difference >, // Let's assume we want the same visibility. pub b: Option< <SomeOtherStruct as Diff>::Difference >, } }
And when we write a template to derive Diff
on an enum MyEnum
,
we'll want our template to define a new enumeration representing the change.
#![allow(unused)] fn main() { pub trait Diff { type Difference: std::fmt::Debug; } impl Diff for String { type Difference = (); } impl Diff for u32 { type Difference = (); } #[derive(Debug,Clone)] struct SomeOtherStruct; impl Diff for SomeOtherStruct { type Difference = (); } // Example enum #[derive(Clone, Debug)] enum MyEnum { A, B(String, String), C { v1: u32, v2: SomeOtherStruct, } } // Expected output #[derive(Debug)] enum MyEnumDiff { // We'll never actually generate this variant: // if two values are both `MyEnum::A`, they have no difference. BothA, /// Both items are MyEnum::B, but some value changed. BothB( Option< <String as Diff>::Difference >, Option< <String as Diff>::Difference >, ), // Both items are MyEnum::C, but some value changed. BothC { v1: Option< <u32 as Diff>::Difference >, v2: Option< <SomeOtherStruct as Diff>::Difference >, }, // The two items are different variants of MyEnum. // // (This assumes that MyEnum implements Debug and Clone.) TypeChanged { original_value: MyEnum, new_value: MyEnum, } } }
Let's try to see how we can make this happen!