kevy_bytes/eq.rs
1//! Equality, specialised on which variant each side is holding.
2//!
3//! Split out of `lib.rs` when that file reached the workspace's 500-line
4//! ceiling. It is a self-contained concern: `PartialEq` branches on the
5//! variant **once** and then compares lengths and bytes directly, rather
6//! than going through `as_slice()` twice and re-deriving the discriminator
7//! on each call. The other trait impls that need only the public slice view
8//! live in `traits.rs`; these are here because they read the union.
9
10use core::slice;
11
12use crate::SmallBytes;
13use crate::heap::INLINE_LEN_MAX;
14
15// `Debug`, `PartialOrd`, `Ord`, `Hash`, `AsRef<[u8]>`, `Borrow<[u8]>`,
16// `KevyHash`, `From<&[u8]>`, `From<Vec<u8>>` live in `crate::traits` —
17// they only need the public `as_slice()` view. `PartialEq` / `Eq` stay
18// here because the same-variant fast paths reach into `self.inline` /
19// `self.heap` directly.
20
21impl SmallBytes {
22 /// Both sides inline: compare tag-lengths, then the inline bytes.
23 /// Single call site in [`PartialEq::eq`]; `inline(always)` keeps the
24 /// split codegen-identical to the pre-split fused body.
25 #[allow(clippy::inline_always)] // see doc above: codegen parity with the pre-split body
26 #[inline(always)]
27 fn eq_inline_inline(&self, other: &Self, self_tag: u8, other_tag: u8) -> bool {
28 let len = self_tag as usize;
29 if len != other_tag as usize {
30 return false;
31 }
32 // SAFETY: the caller reached this arm by finding both tags <= INLINE_LEN_MAX,
33 // so the inline variant is the live one on both sides, and `len` is that tag —
34 // the count of initialised bytes in `inline.data`.
35 let a = unsafe { slice::from_raw_parts(self.inline.data.as_ptr(), len) };
36 // SAFETY: as above, and the equal-length check just above means `other` holds
37 // the same number of initialised bytes.
38 let b = unsafe { slice::from_raw_parts(other.inline.data.as_ptr(), len) };
39 a == b
40 }
41
42 /// Both sides heap: compare stored lengths, then the heap bytes.
43 /// Single call site in [`PartialEq::eq`]; `inline(always)` as above.
44 #[allow(clippy::inline_always)] // see doc above: codegen parity with the pre-split body
45 #[inline(always)]
46 fn eq_heap_heap(&self, other: &Self) -> bool {
47 // SAFETY: both in heap variant.
48 let (a_len, b_len) = unsafe { (self.heap.length(), other.heap.length()) };
49 if a_len != b_len {
50 return false;
51 }
52 // SAFETY: the caller reached this arm with both tags > INLINE_LEN_MAX, so the
53 // heap variant is live on both sides. `heap.ptr` owns an allocation of
54 // `heap.length()` bytes for the life of the value, and `a_len` is that length.
55 let a = unsafe { slice::from_raw_parts(self.heap.ptr.as_ptr(), a_len) };
56 // SAFETY: as above, with `other`'s own pointer and its own length.
57 let b = unsafe { slice::from_raw_parts(other.heap.ptr.as_ptr(), b_len) };
58 a == b
59 }
60}
61
62impl PartialEq for SmallBytes {
63 /// Specialised over the slice form (`as_slice == as_slice`) by branching
64 /// on variant **once** and reading the relevant length / pointer pair
65 /// directly. Same-variant cases (inline/inline + heap/heap, which are the
66 /// only ones produced by a single allocator) skip a redundant `as_slice`
67 /// dispatch on each side; the mixed case falls back to the slice form.
68 #[inline]
69 fn eq(&self, other: &Self) -> bool {
70 // SAFETY: byte 23 (`inline.tag`) is always a valid load in either
71 // variant — it's either the inline-length 0..=23 or 0xFF as the
72 // heap-discriminator overlap (see crate doc).
73 let self_tag = unsafe { self.inline.tag };
74 // SAFETY: same overlap argument, on the other value.
75 let other_tag = unsafe { other.inline.tag };
76 let self_inline = self_tag <= INLINE_LEN_MAX;
77 let other_inline = other_tag <= INLINE_LEN_MAX;
78 match (self_inline, other_inline) {
79 (true, true) => self.eq_inline_inline(other, self_tag, other_tag),
80 (false, false) => self.eq_heap_heap(other),
81 // Mixed inline/heap: this IS reachable in normal operation.
82 // It happens whenever HashMap (or any `==` consumer) compares
83 // an inline-length value (len ≤ 23) against a heap-length
84 // value (len > 23). Two SmallBytes of different lengths can
85 // *collide* on hashbrown's hash + quadratic probe, and the
86 // probe checks equality even though the lengths differ. The
87 // pre-fix `unreachable!()` here was a logic bug — it assumed
88 // the same-arm short-circuits cover all cases, but they only
89 // fire when both sides land in the same arm. Different-length
90 // collisions correctly fall through here. The right answer
91 // is just slice-form equality (which short-circuits on `len`
92 // internally), giving `false` whenever the lengths differ.
93 _ => self.as_slice() == other.as_slice(),
94 }
95 }
96}
97impl Eq for SmallBytes {}