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