You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+31-1Lines changed: 31 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
8
8
## [Unreleased]
9
9
10
+
## [0.4.1] - 2026-04-27
11
+
12
+
### Added
13
+
14
+
-**`pub const FORMAT_VERSION: u32 = 1`** at crate root, plus `ImageFingerprint::format_version()` and `MultiHashFingerprint::format_version()` accessors. Persist alongside fingerprint bytes (or in a sidecar manifest) and refuse comparison across mismatched versions to guard against algorithm-version drift. The constant only changes when the algorithm output changes; layout drift is caught at compile time independently.
15
+
16
+
-**`bytemuck::Pod + Zeroable` derives** on `ImageFingerprint` and `MultiHashFingerprint` with `#[repr(C)]`. Enables zero-copy persistence:
Unblocks UCFP's `index` crate to store millions of fingerprints without serde-roundtrip overhead.
26
+
27
+
-**Compile-time layout assertions** — `const _` size checks that `ImageFingerprint` is exactly 168 bytes and `MultiHashFingerprint` is exactly 536 bytes. Any accidental field reorder or padding addition fails the build, so consumers relying on `bytemuck::cast_slice` can't be silently broken.
28
+
29
+
### Changed
30
+
31
+
-**`ImageFingerprint` and `MultiHashFingerprint` now derive `Copy`** (required by `bytemuck::Pod`). Trade-off: move-by-value silently memcpys 168 / 536 bytes; prefer `&Fingerprint` borrows in hot loops where this matters. Purely additive change — nothing that compiled before fails to compile now.
32
+
33
+
-**`#[repr(C)]`** added to both fingerprint types for stable cross-version binary layout. The default `repr(Rust)` layout was already padding-free at 168 / 536 bytes, so this is a guarantee, not a layout change.
34
+
35
+
### Notes for UCFP integrators
36
+
37
+
Storing fingerprints: cast a `&[MultiHashFingerprint]` to `&[u8]` with `bytemuck::cast_slice` before writing to your index. Persist the `FORMAT_VERSION` constant alongside (in your manifest or as a sidecar header). On read, verify the version matches before casting back. The layout-stability assertions guarantee that within a `FORMAT_VERSION`, the byte representation is identical across builds and platforms with the same endianness.
Copy file name to clipboardExpand all lines: future.md
+34Lines changed: 34 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,6 +13,40 @@ Legend:
13
13
14
14
---
15
15
16
+
## 0. Next Up: 0.4.1 (UCFP-unblocking patch)
17
+
18
+
A small, additive patch that unblocks UCFP's persistence story without
19
+
the structural breakage that 0.5.0 needs.
20
+
21
+
| # | Item | Priority | Effort | Status |
22
+
|---|------|----------|--------|--------|
23
+
| 0.1 |**`pub const FORMAT_VERSION: u32`** at crate root + `MultiHashFingerprint::format_version()` accessor returning the same constant | P0 | S | planned |
24
+
| 0.2 |**`bytemuck::Pod + Zeroable`** derives on `ImageFingerprint` / `MultiHashFingerprint` with `#[repr(C)]` for stable layout | P0 | S | planned |
25
+
| 0.3 |**Zero-copy persistence example** — `bytemuck::cast_slice(&fingerprints)` ↔ `&[u8]` for UCFP's mmap'd index | P0 | S | planned |
26
+
| 0.4 |**Layout-stability test** — `static_assertions::const_assert_eq!(size_of::<MultiHashFingerprint>(), 536)` so any accidental layout drift is caught at compile time | P1 | S | planned |
27
+
28
+
Why this set: UCFP's `index` crate stores millions of fingerprints. Today
29
+
serializing them goes through serde-roundtrip (slow, allocates).
30
+
`bytemuck::cast_slice` gives a zero-copy `&[u8]` view, mmap-friendly. The
31
+
`FORMAT_VERSION` constant lets UCFP refuse cross-version compares without
32
+
embedding a version field in every fingerprint (which would change layout
33
+
and balloon storage).
34
+
35
+
Design choice: using full `Pod + Zeroable`. The earlier plan was to use
36
+
`NoUninit + AnyBitPattern + Zeroable` to avoid `Copy`, but in current
37
+
`bytemuck` (1.18+) those traits also require `Copy`. So `Copy` is
38
+
unavoidable if we want any `cast_slice` capability. Trade-off accepted:
39
+
move-by-value memcpys 168 / 536 bytes; `&Fingerprint` borrows in hot
40
+
loops avoid this. If we ever want to drop `Copy`, we'd switch to the
41
+
`zerocopy` crate (different trait shape) — left as a future option.
42
+
43
+
Not in this patch:
44
+
- Removing the redundant per-algorithm `exact: [u8; 32]` field from inner
45
+
`ImageFingerprint`s (saves 96 bytes per `MultiHashFingerprint`). It's a
46
+
layout break and changes `Hash` derivation; deferred to 0.5.0.
47
+
48
+
---
49
+
16
50
## 1. Configurability Completeness
17
51
18
52
The 0.4.0 release tuned every weight and decode-time guard. What's left
0 commit comments