daft
Daft is a library to perform semantic diffs of Rust data structures.
Daft consists of a trait called Diffable, along with a derive
macro by the same name. This trait represents the
notion of a type for which two members can be simultaneously compared.
Features
- Recursive diffing of structs, sets, and maps
- Derive macro for automatically generating diff types
- Choose between eager and lazy diffing
- No-std compatible, both with and without
alloc
Usage
use ;
// Annotate your struct with `#[derive(Diffable)]`:
// This generates a type called MyStructDiff, which looks like:
// Then, with two instances of MyStruct:
let before = MyStruct ;
let after = MyStruct ;
// You can diff them like so:
let diff = before.diff;
// And compare the results:
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
This crate assigns one side the name before, and the other side after. These labels are arbitrary: if before and after are swapped, the diff is reversed.
Diff types
Currently, daft comes with a few kinds of diff types:
Leaf instances
A Leaf represents a logical leaf node or base case in a diff, i.e. a
point at which diffing stops. Leaf instances are used for:
- Scalar or primitive types like
i32,String,bool, etc. - Enums, since diffing across variants is usually not meaningful.
- Vector and slice types, since there are several reasonable ways to diff vectors (e.g. set-like, ordered, etc.) and we don’t want to make assumptions.
- As an opt-in mechanism for struct fields: see Recursive diffs below for more.
Example
A contrived example for integers:
use ;
let diff: = 1_i32.diff;
assert_eq!;
assert_eq!;
Enums also use Leaf:
use ;
// Option<T> uses Leaf:
let diff: = Some.diff;
assert_eq!;
assert_eq!;
// Automatically derived enums also use Leaf:
let before = A;
let after = B;
let diff: = before.diff;
assert_eq!;
assert_eq!;
Vectors use Leaf as well:
use ;
let before = vec!;
let after = vec!;
let diff: = before.diff;
assert_eq!;
assert_eq!;
Map diffs
For BTreeMap and HashMap, daft has corresponding BTreeMapDiff
and HashMapDiff types. These types have fields for common, added,
and removed entries.
Map diffs are performed eagerly for keys, but values are stored as leaf nodes.
Example
use ;
use BTreeMap;
let mut a = new;
a.insert;
a.insert;
a.insert;
let mut b = new;
b.insert;
b.insert;
b.insert;
let diff: = a.diff;
// Added and removed entries are stored as maps:
assert_eq!;
assert_eq!;
// Common entries are stored as leaf nodes.
assert_eq!;
// If `V` implements `Eq`, unchanged and modified iterators become
// available. `unchanged` and `modified` return key-value pairs;
// `unchanged_keys` and `modified_keys` return keys; and
// `unchanged_values` and `modified_values` return values.
//
// Here's `unchanged_keys` to get the keys of unchanged entries:
assert_eq!;
// `modified_values` returns leaf nodes for modified entries.
assert_eq!;
Set diffs
For BTreeSet and HashSet, daft has corresponding BTreeSetDiff
and HashSetDiff types. These types have fields for common, added,
and removed entries.
Set diffs are performed eagerly.
Example
use ;
use BTreeSet;
let a: = .into_iter.collect;
let b: = .into_iter.collect;
let diff: = a.diff;
assert_eq!;
assert_eq!;
assert_eq!;
Tuple diffs
For a tuple like (A, B, C), the Diffable implementation is recursive:
the diff resolves to (A::Diff, B::Diff, C::Diff).
Example
use ;
use BTreeSet;
let before: = ;
let after = ;
let diff = before.diff;
assert_eq!;
Struct diffs
For structs, the Diffable derive macro generates
a diff type with a field corresponding to each field type. Each field must
implement Diffable.
A struct Foo gets a corresponding FooDiff struct, which has fields
corresponding to each field in Foo.
Struct options
#[daft(leaf)]: if a struct is annotated with this, theDiffableimplementation for the struct will be aLeafinstead of a recursive diff.
Field options
#[daft(leaf)]: if a struct field is annotated with this, the generated struct’s corresponding field will be aLeaf, regardless of the field’sDifftype (or even whether it implementsDiffableat all).#[daft(ignore)]: the generated struct’s corresponding field is not included in the diff.
Example
For an example of structs with named fields, see Usage above.
Tuple-like structs produce tuple-like diff structs:
use Diffable;
use BTreeMap;
;
let before = MyTuple;
let after = MyTuple;
let diff = before.diff;
// The generated type is MyTupleDiff(BTreeMapDiff<i32, &str>, Leaf<i32>).
assert_eq!;
assert_eq!;
assert_eq!;
An example with #[daft(leaf)] on structs:
use ;
let before = MyStruct ;
let after = MyStruct ;
let diff: = before.diff;
assert_eq!;
assert_eq!;
An example with #[daft(leaf)] on struct fields:
use ;
// A simple struct that implements Diffable.
// A struct that does not implement Diffable.
;
let before = OuterStruct ;
let after = OuterStruct ;
let diff = before.diff;
// `OuterStructDiff` does *not* recursively diff `InnerStruct`, but instead
// returns a leaf node.
assert_eq!;
// But you can continue the recursion anyway, since `InnerStruct` implements
// `Diffable`:
let inner_diff = diff.inner.diff_pair;
assert_eq!;
// `PlainStruct` can also be compared even though it doesn't implement `Diffable`.
assert_eq!;
Custom diff types
The Diffable trait can also be implemented manually for custom behavior.
In general, most custom implementations will likely use one of the built-in diff types directly.
Example
Some structs like identifiers should be treated as leaf nodes. This can be
implemented via #[daft(leaf)], but also manually:
use ;
;
Type and lifetime parameters
If a type parameter is specified, the Diffable derive
macro for structs normally requires that the type parameter implement
Diffable. This is not required if the field is annotated with
#[daft(leaf)].
Daft fully supports types with arbitrary lifetimes. Automatically generated
diff structs will have an additional 'daft lifetime parameter at the
beginning, with the requirement that all other lifetime and type parameters
outlive it.
Example
use Diffable;
// This generates a struct that looks like:
Optional features
derive: Enable theDiffablederive macro: disabled by default.
Implementations for standard library types, all enabled by default:
alloc: Enable diffing for types from thealloccrate.std: Enable diffing for types from thestdcrate.
(With default-features = false, daft is no-std compatible.)
Implementations for third-party types, all disabled by default:
uuid1: Enable diffing foruuid::Uuid.oxnet01: Enable diffing for network types from theoxnetcrate.newtype-uuid1: Enable diffing fornewtype_uuid::TypedUuid.
Minimum supported Rust version (MSRV)
The minimum supported Rust version is 1.81.0. At any time, at least the last three stable versions of Rust are supported.
While this crate is a pre-release (0.x.x) it may have its MSRV bumped in a patch release. Once this crate has reached 1.x, any MSRV bump will be accompanied with a new minor version.
Related work
Diffus is the original inspiration for this crate and a great alternative. Daft diverges from diffus in a few ways:
-
Daft’s derive macro does not attempt to diff enums with different variants. In practice, we’ve found that diffing enums across different variants is less useful than it first appears.
-
Daft has the notion of a
Leaftype, which represents an atomic unit. (For example, theDiffableimplementation fori32is aLeaf.)Leafs are also used for enums, as well as in any other place where lazy diffing is desired. -
Diffus has a
Sametrait, which is likeEqexcept it’s also implemented for floats. Daft doesn’t have theSametrait, and its core functionality forgoes the need forEqentirely.For a primitive scalar like
f64, you’ll get aLeafstruct which you can compare with whatever notion of equality you want. -
Daft uses a generic associated type (GAT) so that the
Diffabletrait no longer needs a lifetime parameter. This leads to simpler usage. (Diffus was written before GATs were available in stable Rust.) -
Daft uses fewer types in general. For example, diffus wraps its return values in an outer
Edittype, but daft does not. -
Daft is no-std-compatible, while diffus requires std.
License
This project is available under the terms of either the Apache 2.0 license or the MIT license.