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: both in inline variant; first `len` bytes valid.
33 let a = unsafe { slice::from_raw_parts(self.inline.data.as_ptr(), len) };
34 let b = unsafe { slice::from_raw_parts(other.inline.data.as_ptr(), len) };
35 a == b
36 }
37
38 /// Both sides heap: compare stored lengths, then the heap bytes.
39 /// Single call site in [`PartialEq::eq`]; `inline(always)` as above.
40 #[allow(clippy::inline_always)] // see doc above: codegen parity with the pre-split body
41 #[inline(always)]
42 fn eq_heap_heap(&self, other: &Self) -> bool {
43 // SAFETY: both in heap variant.
44 let (a_len, b_len) = unsafe { (self.heap.length(), other.heap.length()) };
45 if a_len != b_len {
46 return false;
47 }
48 // SAFETY: heap pointers + len are valid.
49 let a = unsafe { slice::from_raw_parts(self.heap.ptr.as_ptr(), a_len) };
50 let b = unsafe { slice::from_raw_parts(other.heap.ptr.as_ptr(), b_len) };
51 a == b
52 }
53}
54
55impl PartialEq for SmallBytes {
56 /// Specialised over the slice form (`as_slice == as_slice`) by branching
57 /// on variant **once** and reading the relevant length / pointer pair
58 /// directly. Same-variant cases (inline/inline + heap/heap, which are the
59 /// only ones produced by a single allocator) skip a redundant `as_slice`
60 /// dispatch on each side; the mixed case falls back to the slice form.
61 #[inline]
62 fn eq(&self, other: &Self) -> bool {
63 // SAFETY: byte 23 (`inline.tag`) is always a valid load in either
64 // variant — it's either the inline-length 0..=22 or 0xFF as the
65 // heap-discriminator overlap (see crate doc).
66 let self_tag = unsafe { self.inline.tag };
67 let other_tag = unsafe { other.inline.tag };
68 let self_inline = self_tag <= INLINE_LEN_MAX;
69 let other_inline = other_tag <= INLINE_LEN_MAX;
70 match (self_inline, other_inline) {
71 (true, true) => self.eq_inline_inline(other, self_tag, other_tag),
72 (false, false) => self.eq_heap_heap(other),
73 // Mixed inline/heap: this IS reachable in normal operation.
74 // It happens whenever HashMap (or any `==` consumer) compares
75 // an inline-length value (len ≤ 22) against a heap-length
76 // value (len > 22). Two SmallBytes of different lengths can
77 // *collide* on hashbrown's hash + quadratic probe, and the
78 // probe checks equality even though the lengths differ. The
79 // pre-fix `unreachable!()` here was a logic bug — it assumed
80 // the same-arm short-circuits cover all cases, but they only
81 // fire when both sides land in the same arm. Different-length
82 // collisions correctly fall through here. The right answer
83 // is just slice-form equality (which short-circuits on `len`
84 // internally), giving `false` whenever the lengths differ.
85 _ => self.as_slice() == other.as_slice(),
86 }
87 }
88}
89impl Eq for SmallBytes {}