Skip to main content

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 {}