Diff

facet-diff provides comprehensive diffing capabilities for any type that implements Facet. It includes compact and tree formats with syntax highlighting and confusable character detection.

Struct field changes

Changes to multiple fields in a struct including nested settings.

age30  31
settings.theme"dark"  "light"
email"alice@example.com"  "alice@newdomain.com"

{     .. 1 unchanged field     age: 30 → 31     email: "alice@example.com" → "alice@newdomain.com"     settings: {         .. 1 unchanged field         theme: "dark" → "light"     } }

Nested structures

Changes to fields deep within nested structures.

sections.[0].heading"Intro"  "Introduction"
sections.[1].content"Some content here"  "Updated content"

Sequences (lists/arrays)

Various operations on sequences including single element changes, insertions, deletions, and reordering.

a) Single element change:
[2]3  99

b) Insertions and deletions: [     - 1     - 2     + 1     + 4     + 5     .. 1 unchanged item ]

c) Reordering: [     + "c"     .. 2 unchanged items     - "c" ]

Enums

Enum diffing including same variant with different data and different variants.

a) Same variant, different data:
::Inactive.reason"vacation"  "sick leave"

b) Different variants: Status::Active  Status::Pending {   since: 42, }

Options

Option types including inner value changes and None to Some transitions.

a) Some to Some (inner change):
email"bob@example.com"  "bob@company.com"
settings.notificationsfalse  true

b) None to Some: None  Some(42)

Many changes (truncated)

Large number of changes that get truncated to show summary.

[2]2  200
[4]4  400
[6]6  600
[8]8  800
[10]10  1000
[12]12  1200
[14]14  1400
[16]16  1600
[18]18  1800
[20]20  2000
... and 4 more changes

No changes

Comparing a value with itself shows no differences.

(no changes)

Scalar types

Diffing primitive types including integers, floats, booleans, characters, and strings.

a) Integers:
  i32: 42  -42
  i128 min→max: -170141183460469231731687303715884105728  170141183460469231731687303715884105727
  u64 0→max: 0  18446744073709551615

b) Floats:   f64: 3.141592653589793  2.718281828459045   f64 inf→-inf: inf  -inf   f64 NaN→NaN: NaN  NaN

c) Booleans:   bool: true  false

d) Characters:   char: A  Z   emoji: 🦀  🐍

e) Strings:   &str: "hello"  "world"   String unicode: "Hello 世界"  "Hello 🌍"

Confusable strings

Detection of Unicode confusable characters using the Unicode TR39 confusables database. These include homoglyphs that look similar but are from different scripts.

a) Latin 'a' vs Cyrillic 'а' (detected):
"abc" → "аbc"
(strings are visually confusable but differ in 1 position):
  [0]: 'a' (U+0061) vs '\u{0430}'

b) Latin 'o' vs Greek 'ο' (detected): "foo" → "fοo" (strings are visually confusable but differ in 1 position):   [1]: 'o' (U+006F) vs '\u{03BF}'

c) Latin 'e' vs Cyrillic 'е' (detected): "hello" → "hеllo" (strings are visually confusable but differ in 1 position):   [1]: 'e' (U+0065) vs '\u{0435}'

d) With zero-width joiner (not in TR39): "test" → "te‍st"

e) Different quote styles (not in TR39): r""quoted"" → "“quoted”"

f) Greek Iota vs Latin I (not in TR39): "userId" → "userΙd"

Byte slices

Diffing byte arrays including ASCII and binary data.

a) ASCII bytes:
  [
    - 104
    - 101
    - 108
    - 108
    + 119
    .. 1 unchanged item
    + 114
    + 108
    + 100
]

b) Binary data:   [     - 0     - 255     - 66     - 19     + 0     + 254     + 66     + 55 ]

c) Vec<u8>:   [2]3  99

Deep tree (6 levels)

Deeply nested structures demonstrating change detection at multiple nesting levels.

a) Change at deepest level (level 6):
inner.inner.inner.inner.inner.tag"original"  "modified"
inner.inner.inner.inner.inner.value42  999

b) Changes at multiple levels (2, 4, 6): inner.inner.inner.inner.inner.value42  100 inner.inner.inner.enabledtrue  false inner.priority1  5

c) Changes at every level: label"label-old"  "label-new" inner.priority1  2 inner.inner.inner.enabledtrue  false inner.inner.inner.inner.count10  20 inner.inner.inner.inner.inner.value1  2 inner.inner.inner.inner.inner.tag"a"  "b" inner.inner.name"old"  "new"

d) Tree format for deep change: {     .. 1 unchanged field     inner: {         .. 1 unchanged field         inner: {             .. 1 unchanged field             inner: {                 .. 1 unchanged field                 inner: {                     .. 1 unchanged field                     inner: {                         .. 1 unchanged field                         value: 42 → 999                     }                 }             }         }     } }

Wide tree (20 fields)

Structure with many fields demonstrating diff truncation and summarization.

a) Single field change (among 20 fields):
field_18300  999

b) Scattered changes (fields 2, 8, 14, 19): field_02"b"  "CHANGED" field_19400  888 field_083  999 field_14true  false

c) Many changes (exceeds truncation limit): field_02"b"  "B" field_05"e"  "E" field_083  30 field_18300  3000 field_094  40 field_11true  false field_01"a"  "A" field_14true  false field_17200  2000 field_13true  false ... and 10 more changes

d) Tree format with few changes: {     .. 19 unchanged fields     field_12: true → false }