SkillAgentSearch skills...

Daft

Ergonomic Type Diffs

Install / Use

/learn @oxidecomputer/Daft
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<!-- cargo-sync-rdme title [[ -->

daft

<!-- cargo-sync-rdme ]] --> <!-- cargo-sync-rdme badge [[ -->

License: MIT OR Apache-2.0 crates.io docs.rs Rust: ^1.81.0

<!-- cargo-sync-rdme ]] --> <!-- cargo-sync-rdme rustdoc [[ -->

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 daft::{Diffable, Leaf};

// Annotate your struct with `#[derive(Diffable)]`:
#[derive(Diffable)]
struct MyStruct {
    a: i32,
    b: String,
}

// This generates a type called MyStructDiff, which looks like:
#[automatically_derived]
struct MyStructDiff<'daft> {
    a: Leaf<&'daft i32>,
    b: Leaf<&'daft str>,
}

// Then, with two instances of MyStruct:
let before = MyStruct { a: 1, b: "hello".to_owned() };
let after = MyStruct { a: 2, b: "world".to_owned() };

// You can diff them like so:
let diff = before.diff(&after);

// And compare the results:
assert_eq!(*diff.a.before, 1);
assert_eq!(*diff.a.after, 2);
assert_eq!(diff.b.before, "hello");
assert_eq!(diff.b.after, "world");

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 daft::{Diffable, Leaf};

let diff: Leaf<&i32> = 1_i32.diff(&2);
assert_eq!(*diff.before, 1);
assert_eq!(*diff.after, 2);

Enums also use Leaf:

use daft::{Diffable, Leaf};

// Option<T> uses Leaf:
let diff: Leaf<Option<&i32>> = Some(1_i32).diff(&Some(2));
assert_eq!(diff.before, Some(&1));
assert_eq!(diff.after, Some(&2));

// Automatically derived enums also use Leaf:
#[derive(Debug, PartialEq, Eq, Diffable)]
enum MyEnum {
    A(i32),
    B(String),
}

let before = MyEnum::A(1);
let after = MyEnum::B("hello".to_string());

let diff: Leaf<&MyEnum> = before.diff(&after);
assert_eq!(diff.before, &before);
assert_eq!(diff.after, &after);

Vectors use Leaf as well:

use daft::{Diffable, Leaf};

let before = vec![1, 2, 3];
let after = vec![4, 5, 6];
let diff: Leaf<&[i32]> = before.diff(&after);
assert_eq!(diff.before, &before);
assert_eq!(diff.after, &after);

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 daft::{Diffable, Leaf, BTreeMapDiff};
use std::collections::BTreeMap;

let mut a = BTreeMap::new();
a.insert(1, "one");
a.insert(2, "two");
a.insert(3, "three");

let mut b = BTreeMap::new();
b.insert(2, "two");
b.insert(3, "THREE");
b.insert(4, "four");

let diff: BTreeMapDiff<'_, i32, &str> = a.diff(&b);

// Added and removed entries are stored as maps:
assert_eq!(diff.added, [(&4, &"four")].into_iter().collect());
assert_eq!(diff.removed, [(&1, &"one")].into_iter().collect());

// Common entries are stored as leaf nodes.
assert_eq!(
    diff.common,
    [
        (&2, Leaf { before: &"two", after: &"two" }),
        (&3, Leaf { before: &"three", after: &"THREE" })
    ]
    .into_iter().collect(),
);

// 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!(diff.unchanged_keys().collect::<Vec<_>>(), [&2]);

// `modified_values` returns leaf nodes for modified entries.
assert_eq!(
    diff.modified_values().collect::<Vec<_>>(),
    [Leaf { before: &"three", after: &"THREE" }],
);

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 daft::{Diffable, Leaf, BTreeSetDiff};
use std::collections::BTreeSet;

let a: BTreeSet<i32> = [0, 1, 2, 3, 4, 5].into_iter().collect();
let b: BTreeSet<i32> = [3, 4, 5, 6, 7, 8].into_iter().collect();
let diff: BTreeSetDiff<'_, i32> = a.diff(&b);

assert_eq!(diff.common, [&3, &4, &5].into_iter().collect());
assert_eq!(diff.added, [&6, &7, &8].into_iter().collect());
assert_eq!(diff.removed, [&0, &1, &2].into_iter().collect());

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 daft::{BTreeSetDiff, Diffable, Leaf};
use std::collections::BTreeSet;

let before: (usize, String, BTreeSet<usize>) = (1, "hello".to_owned(), [1, 2, 3].into_iter().collect());
let after = (2, "world".to_owned(), [2, 3, 4].into_iter().collect());

let diff = before.diff(&after);
assert_eq!(
    diff,
    (
        Leaf { before: &1, after: &2 },
        Leaf { before: "hello", after: "world" },
        BTreeSetDiff {
            common: [&2, &3].into_iter().collect(),
            added: [&4].into_iter().collect(),
            removed: [&1].into_iter().collect(),
        }
    ),
);

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, the Diffable implementation for the struct will be a Leaf instead of a recursive diff.
Field options
  • #[daft(leaf)]: if a struct field is annotated with this, the generated struct’s corresponding field will be a Leaf, regardless of the field’s Diff type (or even whether it implements Diffable at 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 daft::Diffable;
use std::collections::BTreeMap;

#[derive(Diffable)]
struct MyTuple(BTreeMap<i32, &'static str>, i32);

let before = MyTuple(BTreeMap::new(), 1);
let after = MyTuple([(1, "hello")].into_iter().collect(), 2);
let diff = before.diff(&after);

// The generated type is MyTupleDiff(BTreeMapDiff<i32, &str>, Leaf<i32>).
assert_eq!(**diff.0.added.get(&1).unwrap(), "hello");
assert_eq!(*diff.1.before, 1);
assert_eq!(*diff.1.after, 2);

An example with #[daft(leaf)] on structs:

use daft::{Diffable, Leaf};

#[derive(Diffable)]
#[daft(leaf)]
struct MyStruct {
    a: i32,
}

let before = MyStruct { a: 1 };
let after = MyStruct { a: 2 };
let diff: Leaf<&MyStruct> = before.diff(&after);

assert_eq!(diff.before.a, 1);
assert_eq!(diff.after.a, 2);

An example with #[daft(leaf)] on struct fields:

use daft::{Diffable, Leaf};

// A simple struct that implements Diffable.
#[derive(Debug, PartialEq, Eq, Diffable)]
struct InnerStruct {
    text: &'static str,
}

// A struct that does not implement Diffable.
#[derive(Debug, PartialEq, Eq)]
struct PlainStruct(usize);

#[derive(Diffable)]
struct OuterStruct {
    // Ordinarily, InnerStruct would be diffed recursively, but
    // with #[daft(leaf)], it is treated as a leaf node.
    #[daft(leaf)]
    inner: InnerStruct,

    // PlainStruct does not implement Diffable, but using
    // daft(leaf) allows it to be diffed anyway.
    #[daft(leaf)]
    plain: Plai
View on GitHub
GitHub Stars67
CategoryDevelopment
Updated1d ago
Forks6

Languages

Rust

Security Score

95/100

Audited on Apr 1, 2026

No findings