compact_bytes/
growable.rs

1use std::fmt;
2use std::hash::{Hash, Hasher};
3use std::mem::ManuallyDrop;
4use std::ops::Deref;
5use std::ops::DerefMut;
6use std::ptr::NonNull;
7
8use crate::{HeapBytesGrowable, InlineBytes23, INLINE_MASK};
9
10/// [`CompactBytes`] inlines up-to 23 bytes on the stack, if more than that is required we spill to
11/// the heap. The heap representation is not reference counted like `bytes::Bytes`, it's just an
12/// owned blob of bytes.
13///
14/// # Why?
15///
16/// ### 1. Do we want to do this?
17///
18/// Performance. A `Vec<u8>` is already 24 bytes on the stack, and then another allocation on the
19/// heap. If we can avoid the heap allocation altogether it saves memory and improves runtime
20/// performance.
21///
22/// ### 2. Did we write our own implementation?
23///
24/// At the time of writing (October 2023), there isn't anything else in the Rust ecosystem that
25/// provides what we need. There is `smallvec` (which we used to use) but it's not space efficient.
26/// A `SmallVec<[u8; 24]>` required 32 bytes on the stack, so we were wasting 8 bytes! There are
27/// other small vector crates (e.g. `arrayvec` or `tinyvec`) but they have their own limitations.
28/// There are also a number of small string optimizations in the Rust ecosystem, but none of them
29/// work for other various reasons.
30///
31/// # How does this work?
32///
33/// A [`CompactBytes`] is 24 bytes on the stack (same as `Vec<u8>`) but it has two modes:
34///
35/// 1. Heap   `[ ptr<8> | len<8> | cap<8> ]`
36/// 2. Inline `[   buffer<23>   | len <1> ]`
37///
38/// We use the most significant bit of the last byte to indicate which mode we're in.
39///
40pub union CompactBytes {
41    heap: ManuallyDrop<HeapBytesGrowable>,
42    inline: InlineBytes23,
43}
44
45// SAFETY: It is safe to Send a `CompactBytes` to other threads because it owns all of its data.
46unsafe impl Send for CompactBytes {}
47
48// SAFETY: It is safe to share references of `CompactBytes` between threads because it does not
49// support any kind of interior mutability, or other way to introduce races.
50unsafe impl Sync for CompactBytes {}
51
52static_assertions::assert_eq_align!(
53    InlineBytes23,
54    HeapBytesGrowable,
55    CompactBytes,
56    Vec<u8>,
57    usize
58);
59static_assertions::assert_eq_size!(InlineBytes23, HeapBytesGrowable, CompactBytes, Vec<u8>);
60
61static_assertions::const_assert_eq!(std::mem::size_of::<CompactBytes>(), 24);
62
63impl CompactBytes {
64    /// The maximum amount of bytes that a [`CompactBytes`] can store inline.
65    pub const MAX_INLINE: usize = 23;
66
67    /// The minimum amount of bytes that a [`CompactBytes`] will store on the heap.
68    pub const MIN_HEAP: usize = HeapBytesGrowable::MIN_ALLOCATION_SIZE;
69    /// The maximum amount of bytes that a [`CompactBytes`] can store on the heap.
70    pub const MAX_HEAP: usize = HeapBytesGrowable::MAX_ALLOCATION_SIZE;
71
72    /// Creates a new [`CompactBytes`] from the provided slice. Stores the bytes inline if small
73    /// enough.
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use compact_bytes::CompactBytes;
79    ///
80    /// let inline = CompactBytes::new(&[1, 2, 3, 4]);
81    /// assert!(!inline.spilled());
82    /// assert_eq!(inline.len(), 4);
83    ///
84    /// let heap = CompactBytes::new(b"I am a bytes type that will get stored on the heap");
85    /// assert!(heap.spilled());
86    /// assert_eq!(heap.len(), 50);
87    /// ```
88    #[inline]
89    pub fn new(slice: &[u8]) -> Self {
90        if slice.len() <= Self::MAX_INLINE {
91            // SAFETY: We just checked that slice length is less than or equal to MAX_INLINE.
92            let inline = unsafe { InlineBytes23::new(slice) };
93            CompactBytes { inline }
94        } else {
95            let heap = ManuallyDrop::new(HeapBytesGrowable::new(slice));
96            CompactBytes { heap }
97        }
98    }
99
100    /// Creates a new [`CompactBytes`] with the specified capacity, but with a minimum of
101    /// [`CompactBytes::MAX_INLINE`].
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use compact_bytes::CompactBytes;
107    ///
108    /// let min = CompactBytes::with_capacity(4);
109    /// assert_eq!(min.capacity(), CompactBytes::MAX_INLINE);
110    /// ```
111    #[inline]
112    pub fn with_capacity(capacity: usize) -> Self {
113        if capacity <= Self::MAX_INLINE {
114            let inline = InlineBytes23::empty();
115            CompactBytes { inline }
116        } else {
117            let heap = ManuallyDrop::new(HeapBytesGrowable::with_capacity(capacity));
118            CompactBytes { heap }
119        }
120    }
121
122    /// Creates a new empty [`CompactBytes`] a capacity of [`CompactBytes::MAX_INLINE`]. The
123    /// function can be called in `const` contexts.
124    ///
125    /// # Examples
126    ///
127    /// ```
128    /// use compact_bytes::CompactBytes;
129    ///
130    /// let min = CompactBytes::empty();
131    /// assert_eq!(min.capacity(), CompactBytes::MAX_INLINE);
132    /// ```
133    #[inline]
134    pub const fn empty() -> Self {
135        let inline = InlineBytes23::empty();
136        CompactBytes { inline }
137    }
138
139    /// Creates a new [`CompactBytes`] using the provided pointer, length, and capacity.
140    ///
141    /// # Safety
142    ///
143    /// * The caller must guarantee that the provided pointer is properly aligned, and the backing
144    ///   allocation was made by the same allocator that will eventually be used to free the
145    ///   returned [`CompactBytes`].
146    /// * `length` needs to be less than or equal to `capacity`.
147    /// * `capacity` needs to be the capacity that the pointer was allocated with.
148    /// * `capacity` needs to be less than or equal to [`CompactBytes::MAX_HEAP`].
149    ///
150    #[inline]
151    pub unsafe fn from_raw_parts(ptr: *mut u8, length: usize, capacity: usize) -> Self {
152        let heap = HeapBytesGrowable {
153            ptr: NonNull::new_unchecked(ptr),
154            len: length,
155            cap: capacity,
156        };
157        let heap = ManuallyDrop::new(heap);
158        CompactBytes { heap }
159    }
160
161    /// Returns the contents of the [`CompactBytes`] as a bytes slice.
162    #[inline]
163    pub fn as_slice(&self) -> &[u8] {
164        let pointer = self.as_ptr();
165        let length = self.len();
166
167        unsafe { core::slice::from_raw_parts(pointer, length) }
168    }
169
170    /// Returns the contents of the [`CompactBytes`] as a mutable bytes slice.
171    #[inline]
172    pub fn as_mut_slice(&mut self) -> &mut [u8] {
173        let pointer = self.as_mut_ptr();
174        let length = self.len();
175
176        unsafe { core::slice::from_raw_parts_mut(pointer, length) }
177    }
178
179    /// Returns the length of the [`CompactBytes`].
180    #[inline(always)]
181    pub fn len(&self) -> usize {
182        // SAFETY: `InlineBytes` and `HeapBytes` share the same size and alignment. Before
183        // returning this value we check whether it's valid or not.
184        //
185        // Note: This code is very carefully written so we can benefit from branchless
186        // instructions.
187        let (mut length, heap_length) = unsafe { (self.inline.len(), self.heap.len) };
188        if self.spilled() {
189            length = heap_length;
190        }
191
192        length
193    }
194
195    /// Returns if the [`CompactBytes`] is empty.
196    #[inline]
197    pub fn is_empty(&self) -> bool {
198        self.len() == 0
199    }
200
201    /// Returns the capacity of the [`CompactBytes`].
202    #[inline(always)]
203    pub fn capacity(&self) -> usize {
204        // SAFETY: `InlineBytes23` and `HeapBytesGrowable` share the same size
205        // and alignment. Before returning this value we check whether it's
206        // valid or not.
207        //
208        // Note: This code is very carefully written so we can benefit from branchless
209        // instructions.
210        let (mut capacity, heap_capacity) = unsafe { (Self::MAX_INLINE, self.heap.cap) };
211        if self.spilled() {
212            capacity = heap_capacity;
213        }
214        capacity
215    }
216
217    /// Appends an additional byte to the [`CompactBytes`], resizing if necessary.
218    ///
219    /// Note: You should almost never call this in a loop, instead use
220    /// [`CompactBytes::extend_from_slice`].
221    #[inline]
222    pub fn push(&mut self, byte: u8) {
223        self.extend_from_slice(&[byte]);
224    }
225
226    /// Extends the [`CompactBytes`] with bytes from `slice`, resizing if necessary.
227    #[inline(always)]
228    pub fn extend_from_slice(&mut self, slice: &[u8]) {
229        // Reserve at least enough space to fit slice.
230        self.reserve(slice.len());
231
232        let (ptr, len, cap) = self.as_mut_triple();
233        // SAFTEY: `len` is less than `cap`, so we know it's within the original allocation. This
234        // addition does not overflow `isize`, nor does it rely on any wrapping logic.
235        let push_ptr = unsafe { ptr.add(len) };
236
237        debug_assert!((cap - len) >= slice.len(), "failed to reserve enough space");
238
239        // Safety:
240        //
241        // * src is valid for a read of len bytes, since len comes from src.
242        // * dst is valid for writes of len bytes, since we just reserved extra space.
243        // * src and dst are both properly aligned.
244        // * src and dst to not overlap because we have a unique reference to dst.
245        //
246        unsafe { std::ptr::copy_nonoverlapping(slice.as_ptr(), push_ptr, slice.len()) };
247
248        // SAFETY: We just wrote an additional len bytes, so we know this length is valid.
249        unsafe { self.set_len(len + slice.len()) };
250    }
251
252    /// Truncates this [`CompactBytes`], removing all contents but without effecting the capacity.
253    #[inline]
254    pub fn clear(&mut self) {
255        self.truncate(0);
256    }
257
258    /// Truncates this [`CompactBytes`] to the specified length without effecting the capacity. Has
259    /// no effect if `new_len` is greater than the current length.
260    #[inline]
261    pub fn truncate(&mut self, new_len: usize) {
262        if new_len >= self.len() {
263            return;
264        }
265        unsafe { self.set_len(new_len) }
266    }
267
268    /// Reserves at least `additional` bytes for this [`CompactBytes`], possibly re-allocating if
269    /// there is not enough remaining capacity.
270    ///
271    /// # Examples
272    ///
273    /// ```
274    /// use compact_bytes::CompactBytes;
275    ///
276    /// let mut b = CompactBytes::new(b"foo");
277    /// b.reserve(100);
278    ///
279    /// assert_eq!(b.capacity(), 103);
280    /// ```
281    #[inline]
282    pub fn reserve(&mut self, additional: usize) {
283        let len = self.len();
284        let needed_capacity = len
285            .checked_add(additional)
286            .expect("attempt to reserve more than usize::MAX");
287
288        // Already have enough space, nothing to do!
289        if self.capacity() >= needed_capacity {
290            return;
291        }
292
293        // Note: We move the actual re-allocation code path into its own function
294        // so the common case of calling `reserve(...)` when we already have
295        // enough capacity can be inlined by LLVM.
296        realloc(self, len, additional);
297
298        #[cold]
299        fn realloc(this: &mut CompactBytes, len: usize, additional: usize) {
300            // Note: Here we are making a distinct choice to _not_ eagerly inline.
301            //
302            // `CompactBytes`s can get re-used, e.g. calling `CompactBytes::clear`, at which point it's
303            // possible  we could have a length of 0, and 'additional' bytes would be less then
304            // `MAX_INLINE`. Some implementations might opt to drop the existing heap allocation, but
305            // if a `CompactBytes` is being re-used it's likely we'll need the full original capacity,
306            // thus we do not eagerly inline.
307
308            if !this.spilled() {
309                let heap = HeapBytesGrowable::with_additional(this.as_slice(), additional);
310                *this = CompactBytes {
311                    heap: ManuallyDrop::new(heap),
312                };
313            } else {
314                // SAFETY: `InlineBytes` and `HeapBytes` have the same size and alignment. We also
315                // checked above that the current `CompactBytes` is heap allocated.
316                let heap_row = unsafe { &mut this.heap };
317
318                let amortized_capacity = HeapBytesGrowable::amortized_growth(len, additional);
319
320                // First attempt to resize the existing allocation, if that fails then create a new one.
321                if heap_row.realloc(amortized_capacity).is_err() {
322                    let heap = HeapBytesGrowable::with_additional(this.as_slice(), additional);
323                    let heap = ManuallyDrop::new(heap);
324                    *this = CompactBytes { heap };
325                }
326            }
327        }
328    }
329
330    /// Consumes the [`CompactBytes`], returning a `Vec<u8>`.
331    #[inline]
332    pub fn into_vec(self) -> Vec<u8> {
333        if self.spilled() {
334            // SAFETY: `InlineBytes23` and `HeapBytesGrowable` have the same
335            // size and alignment. We also checked above that the current
336            // `CompactBytes` is heap allocated.
337            let heap = unsafe { &self.heap };
338            let vec = unsafe { Vec::from_raw_parts(heap.ptr.as_ptr(), heap.len, heap.cap) };
339            std::mem::forget(self);
340
341            vec
342        } else {
343            self.as_slice().to_vec()
344        }
345    }
346
347    /// Returns if the [`CompactBytes`] has spilled to the heap.
348    #[inline(always)]
349    pub fn spilled(&self) -> bool {
350        // SAFETY: `InlineBytes23` and `HeapBytesGrowable` have the same size
351        // and alignment. We also checked above that the current `CompactBytes`
352        // is heap allocated.
353        unsafe { self.inline.data < INLINE_MASK }
354    }
355
356    /// Forces the length of [`CompactBytes`] to `new_len`.
357    ///
358    /// # Safety
359    /// * `new_len` must be less than or equal to capacity.
360    /// * The bytes at `old_len..new_len` must be initialized.
361    ///
362    #[inline]
363    unsafe fn set_len(&mut self, new_len: usize) {
364        if self.spilled() {
365            self.heap.set_len(new_len);
366        } else {
367            self.inline.set_len(new_len);
368        }
369    }
370
371    #[inline(always)]
372    fn as_ptr(&self) -> *const u8 {
373        // SAFETY: `InlineBytes23` and `HeapBytesGrowable` share the same size
374        // and alignment. Before returning this value we check whether it's
375        // valid or not.
376        //
377        // Note: This code is very carefully written so we can benefit from branchless
378        // instructions.
379        let mut pointer = self as *const Self as *const u8;
380        if self.spilled() {
381            pointer = unsafe { self.heap.ptr }.as_ptr()
382        }
383        pointer
384    }
385
386    #[inline(always)]
387    fn as_mut_ptr(&mut self) -> *mut u8 {
388        // SAFETY: `InlineBytes23` and `HeapBytesGrowable` share the same size
389        // and alignment. Before returning this value we check whether it's
390        // valid or not.
391        //
392        // Note: This code is very carefully written so we can benefit from branchless
393        // instructions.
394        let mut pointer = self as *mut Self as *mut u8;
395        if self.spilled() {
396            pointer = unsafe { self.heap.ptr }.as_ptr()
397        }
398        pointer
399    }
400
401    #[inline(always)]
402    fn as_mut_triple(&mut self) -> (*mut u8, usize, usize) {
403        let ptr = self.as_mut_ptr();
404        let len = self.len();
405        let cap = self.capacity();
406
407        (ptr, len, cap)
408    }
409}
410
411impl Default for CompactBytes {
412    #[inline]
413    fn default() -> Self {
414        CompactBytes::new(&[])
415    }
416}
417
418impl Deref for CompactBytes {
419    type Target = [u8];
420
421    #[inline]
422    fn deref(&self) -> &[u8] {
423        self.as_slice()
424    }
425}
426
427impl DerefMut for CompactBytes {
428    #[inline]
429    fn deref_mut(&mut self) -> &mut [u8] {
430        self.as_mut_slice()
431    }
432}
433
434impl AsRef<[u8]> for CompactBytes {
435    #[inline]
436    fn as_ref(&self) -> &[u8] {
437        self.as_slice()
438    }
439}
440
441impl<T: AsRef<[u8]>> PartialEq<T> for CompactBytes {
442    #[inline]
443    fn eq(&self, other: &T) -> bool {
444        self.as_slice() == other.as_ref()
445    }
446}
447
448impl Eq for CompactBytes {}
449
450impl Hash for CompactBytes {
451    fn hash<H: Hasher>(&self, state: &mut H) {
452        self.as_slice().hash(state)
453    }
454}
455
456impl fmt::Debug for CompactBytes {
457    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458        write!(f, "{:?}", self.as_slice())
459    }
460}
461
462impl Drop for CompactBytes {
463    #[inline]
464    fn drop(&mut self) {
465        // Note: we hint to the compiler that dropping a heap variant is cold to improve the
466        // performance of dropping the inline variant.
467        #[cold]
468        fn outlined_drop(this: &mut CompactBytes) {
469            let heap = unsafe { &mut this.heap };
470            heap.dealloc();
471        }
472
473        if self.spilled() {
474            outlined_drop(self);
475        }
476    }
477}
478
479impl Clone for CompactBytes {
480    #[inline]
481    fn clone(&self) -> Self {
482        // Note: we hint to the compiler that cloing a heap variant is cold to improve the
483        // performance of cloning the inline variant.
484        #[cold]
485        fn outlined_clone(this: &CompactBytes) -> CompactBytes {
486            CompactBytes::new(this.as_slice())
487        }
488
489        if self.spilled() {
490            outlined_clone(self)
491        } else {
492            let inline = unsafe { &self.inline };
493            CompactBytes { inline: *inline }
494        }
495    }
496
497    #[inline]
498    fn clone_from(&mut self, source: &Self) {
499        self.clear();
500        self.extend_from_slice(source.as_slice());
501    }
502}
503
504impl From<Vec<u8>> for CompactBytes {
505    #[inline]
506    fn from(mut value: Vec<u8>) -> Self {
507        if value.is_empty() {
508            let inline = InlineBytes23::empty();
509            return CompactBytes { inline };
510        }
511
512        // Deconstruct the Vec so we can convert to a `CompactBytes` in constant time.
513        let (ptr, len, cap) = (value.as_mut_ptr(), value.len(), value.capacity());
514        // SAFETY: We checked above, and returned early, if the `Vec` was empty, thus we know this
515        // pointer is not null.
516        let ptr = unsafe { NonNull::new_unchecked(ptr) };
517        // Forget the original Vec so it's underlying buffer does not get dropped.
518        std::mem::forget(value);
519
520        let heap = HeapBytesGrowable { ptr, len, cap };
521        CompactBytes {
522            heap: ManuallyDrop::new(heap),
523        }
524    }
525}
526
527impl serde::Serialize for CompactBytes {
528    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
529        self.as_slice().serialize(serializer)
530    }
531}
532
533impl<'de> serde::Deserialize<'de> for CompactBytes {
534    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
535        deserialize_compact_bytes(deserializer)
536    }
537}
538
539fn deserialize_compact_bytes<'de: 'a, 'a, D: serde::Deserializer<'de>>(
540    deserializer: D,
541) -> Result<CompactBytes, D::Error> {
542    struct CompactBytesVisitor;
543
544    impl<'a> serde::de::Visitor<'a> for CompactBytesVisitor {
545        type Value = CompactBytes;
546
547        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
548            formatter.write_str("bytes")
549        }
550
551        fn visit_seq<A: serde::de::SeqAccess<'a>>(
552            self,
553            mut seq: A,
554        ) -> Result<Self::Value, A::Error> {
555            let mut bytes = CompactBytes::default();
556            if let Some(capacity_hint) = seq.size_hint() {
557                bytes.reserve(capacity_hint);
558            }
559
560            while let Some(elem) = seq.next_element::<u8>()? {
561                bytes.push(elem)
562            }
563
564            Ok(bytes)
565        }
566
567        fn visit_borrowed_bytes<E: serde::de::Error>(self, v: &'a [u8]) -> Result<Self::Value, E> {
568            Ok(CompactBytes::new(v))
569        }
570
571        fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
572            Ok(CompactBytes::new(v))
573        }
574    }
575
576    deserializer.deserialize_bytes(CompactBytesVisitor)
577}
578
579#[cfg(test)]
580mod test {
581    use proptest::prelude::*;
582    use test_case::test_case;
583    use test_strategy::proptest;
584
585    use super::{CompactBytes, HeapBytesGrowable};
586
587    #[test]
588    fn test_bitcode() {
589        let obj = CompactBytes::new(b"hello world");
590        let encoded = bitcode::serialize(&obj).unwrap();
591        let decoded: CompactBytes = bitcode::deserialize(&encoded).unwrap();
592        assert_eq!(obj.as_slice(), decoded.as_slice());
593    }
594
595    #[test]
596    fn test_empty() {
597        let obj = const { CompactBytes::empty() };
598        assert_eq!(obj.as_slice(), [0u8; 0].as_slice());
599        assert!(obj.is_empty());
600        assert!(!obj.spilled())
601    }
602
603    #[test]
604    #[cfg_attr(miri, ignore)]
605    fn test_discriminant() {
606        let mut buf = vec![0u8; 32];
607        let heap = HeapBytesGrowable {
608            ptr: unsafe { std::ptr::NonNull::new_unchecked(buf.as_mut_ptr()) },
609            len: 0,
610            cap: usize::MAX >> 1,
611        };
612        let repr = CompactBytes {
613            heap: std::mem::ManuallyDrop::new(heap),
614        };
615        assert!(repr.spilled());
616        // mem::forget the repr since it's underlying buffer is shared.
617        std::mem::forget(repr);
618
619        let bad_heap = HeapBytesGrowable {
620            ptr: unsafe { std::ptr::NonNull::new_unchecked(buf.as_mut_ptr()) },
621            len: 0,
622            cap: usize::MAX,
623        };
624        let repr = CompactBytes {
625            heap: std::mem::ManuallyDrop::new(bad_heap),
626        };
627        // This will identify as inline since the MSB is 1.
628        assert!(!repr.spilled());
629        // mem::forget the repr since it's underlying buffer is shared.
630        std::mem::forget(repr);
631    }
632
633    #[test_case(&[], 0 ; "empty")]
634    #[test_case(b"hello world", 11 ; "short")]
635    #[test_case(b"can fit 23 bytes inline", 23 ; "max_inline")]
636    #[test_case(b"24 bytes and will spill!", 24 ; "first_spill")]
637    #[test_case(b"i am very large and will spill to the heap", 42 ; "heap")]
638    fn smoketest_row(slice: &[u8], expected_len: usize) {
639        let repr = CompactBytes::new(slice);
640
641        assert_eq!(repr.len(), expected_len);
642        assert_eq!(repr.as_slice(), slice);
643        assert_eq!(repr.spilled(), expected_len > CompactBytes::MAX_INLINE);
644    }
645
646    #[test_case(&[], &[] ; "empty_empty")]
647    #[test_case(&[], &[1, 2, 3, 4] ; "empty_inline")]
648    #[test_case(&[], b"once extended I will end up on the heap" ; "empty_heap")]
649    #[test_case(&[1, 2], &[3, 4] ; "inline_inline")]
650    #[test_case(&[1, 2, 3, 4], b"i am some more bytes, i will be on the heap, woohoo!" ; "inline_heap")]
651    #[test_case(b"this row will start on the heap because it's large", b"and this will keep it on the heap" ; "heap_heap")]
652    fn smoketest_extend(initial: &[u8], other: &[u8]) {
653        let mut repr = CompactBytes::new(initial);
654        repr.extend_from_slice(other);
655
656        let mut control = initial.to_vec();
657        control.extend_from_slice(other);
658
659        assert_eq!(repr.len(), control.len());
660        assert_eq!(repr.as_slice(), control.as_slice());
661    }
662
663    #[test_case(&[] ; "empty")]
664    #[test_case(b"i am smol" ; "inline")]
665    #[test_case(b"i am large and will end up on the heap" ; "heap")]
666    fn smoketest_clear(initial: &[u8]) {
667        let mut repr = CompactBytes::new(initial);
668        let capacity = repr.capacity();
669        assert_eq!(repr.as_slice(), initial);
670
671        repr.clear();
672
673        assert!(repr.as_slice().is_empty());
674        assert_eq!(repr.len(), 0);
675
676        // The capacity should not change after clearing.
677        assert_eq!(repr.capacity(), capacity);
678    }
679
680    #[test_case(&[] ; "empty")]
681    #[test_case(b"smol" ; "inline")]
682    #[test_case(b"large large large large large large" ; "heap")]
683    fn smoketest_clone(initial: &[u8]) {
684        let repr_a = CompactBytes::new(initial);
685        let repr_b = repr_a.clone();
686
687        assert_eq!(repr_a.len(), repr_b.len());
688        assert_eq!(repr_a.capacity(), repr_b.capacity());
689        assert_eq!(repr_a.as_slice(), repr_b.as_slice());
690    }
691
692    #[test_case(&[], &[], false ; "empty_empty")]
693    #[test_case(&[], b"hello", false ; "empty_inline")]
694    #[test_case(&[], b"I am long and will be on the heap", true ; "empty_heap")]
695    #[test_case(b"short", &[], false ; "inline_empty")]
696    #[test_case(b"hello", b"world", false ; "inline_inline")]
697    #[test_case(b"i am short", b"I am long and will be on the heap", true ; "inline_heap")]
698    fn smoketest_clone_from(a: &[u8], b: &[u8], should_reallocate: bool) {
699        let mut a = CompactBytes::new(a);
700        let a_capacity = a.capacity();
701        let a_pointer = a.as_slice().as_ptr();
702
703        let b = CompactBytes::new(b);
704
705        // If there is enough capacity in `a`, it's buffer should get re-used.
706        a.clone_from(&b);
707
708        assert_eq!(a.capacity() != a_capacity, should_reallocate);
709        assert_eq!(a.as_slice().as_ptr() != a_pointer, should_reallocate);
710    }
711
712    #[test_case(vec![] ; "empty")]
713    #[test_case(vec![0, 1, 2, 3, 4] ; "inline")]
714    #[test_case(b"I am long and will be on the heap, yada yada yada".to_vec() ; "heap")]
715    fn smoketest_from_vec(initial: Vec<u8>) {
716        let control = initial.clone();
717        let pointer = initial.as_ptr();
718        let repr = CompactBytes::from(initial);
719
720        assert_eq!(control.len(), repr.len());
721        assert_eq!(control.as_slice(), repr.as_slice());
722
723        // We do not eagerly inline, except if the Vec is empty.
724        assert_eq!(repr.spilled(), !control.is_empty());
725        // The allocation of the Vec should get re-used.
726        assert_eq!(repr.as_ptr() == pointer, !control.is_empty());
727    }
728
729    #[test]
730    fn test_cloning_inlines() {
731        let mut c = CompactBytes::with_capacity(48);
732        c.push(42);
733
734        assert_eq!(c.as_slice(), &[42]);
735        assert_eq!(c.capacity(), 48);
736        assert!(c.spilled());
737
738        let clone = c.clone();
739        assert_eq!(clone.as_slice(), &[42]);
740        assert_eq!(clone.capacity(), CompactBytes::MAX_INLINE);
741        assert!(!clone.spilled());
742    }
743
744    #[test]
745    fn test_cloning_drops_excess_capacity() {
746        let mut c = CompactBytes::with_capacity(48);
747        c.extend_from_slice(&[42; 32]);
748
749        assert_eq!(c.as_slice(), &[42; 32]);
750        assert_eq!(c.capacity(), 48);
751        assert_eq!(c.len(), 32);
752        assert!(c.spilled());
753
754        let clone = c.clone();
755        assert_eq!(clone.as_slice(), &[42; 32]);
756        assert_eq!(clone.capacity(), 32);
757        assert_eq!(clone.capacity(), clone.len());
758        assert!(clone.spilled());
759    }
760
761    #[proptest]
762    #[cfg_attr(miri, ignore)]
763    fn proptest_row(initial: Vec<u8>) {
764        let repr = CompactBytes::new(&initial);
765
766        prop_assert_eq!(repr.as_slice(), initial.as_slice());
767        prop_assert_eq!(repr.len(), initial.len());
768    }
769
770    #[proptest]
771    #[cfg_attr(miri, ignore)]
772    fn proptest_extend(initial: Vec<u8>, other: Vec<u8>) {
773        let mut repr = CompactBytes::new(&initial);
774        repr.extend_from_slice(other.as_slice());
775
776        let mut control = initial;
777        control.extend_from_slice(other.as_slice());
778
779        prop_assert_eq!(repr.as_slice(), control.as_slice());
780        prop_assert_eq!(repr.len(), control.len());
781    }
782
783    #[proptest]
784    #[cfg_attr(miri, ignore)]
785    fn proptest_clear(initial: Vec<u8>) {
786        let mut repr = CompactBytes::new(&initial);
787        let capacity = repr.capacity();
788
789        repr.clear();
790        assert!(repr.as_slice().is_empty());
791        assert_eq!(repr.len(), 0);
792
793        // Capacity should not have changed after clear.
794        assert_eq!(repr.capacity(), capacity);
795    }
796
797    #[proptest]
798    #[cfg_attr(miri, ignore)]
799    fn proptest_clear_then_extend(initial: Vec<u8>, a: Vec<u8>) {
800        let mut repr = CompactBytes::new(&initial);
801        let capacity = repr.capacity();
802        let pointer = repr.as_slice().as_ptr();
803
804        repr.clear();
805        assert!(repr.as_slice().is_empty());
806        assert_eq!(repr.len(), 0);
807
808        // Capacity should not have changed after clear.
809        assert_eq!(repr.capacity(), capacity);
810
811        repr.extend_from_slice(&a);
812        assert_eq!(repr.as_slice(), &a);
813        assert_eq!(repr.len(), a.len());
814
815        // If we originall had capacity for the new extension, we should not re-allocate.
816        if a.len() < capacity {
817            assert_eq!(repr.capacity(), capacity);
818            assert_eq!(repr.as_slice().as_ptr(), pointer);
819        }
820    }
821
822    #[proptest]
823    #[cfg_attr(miri, ignore)]
824    fn proptest_clone(initial: Vec<u8>) {
825        let repr_a = CompactBytes::new(&initial);
826        let repr_b = repr_a.clone();
827
828        assert_eq!(repr_a.len(), repr_b.len());
829        assert_eq!(repr_a.capacity(), repr_b.capacity());
830        assert_eq!(repr_a.as_slice(), repr_b.as_slice());
831    }
832
833    #[proptest]
834    #[cfg_attr(miri, ignore)]
835    fn proptest_from_vec(initial: Vec<u8>) {
836        let control = initial.clone();
837        let pointer = initial.as_ptr();
838        let repr = CompactBytes::from(initial);
839
840        assert_eq!(control.len(), repr.len());
841        assert_eq!(control.as_slice(), repr.as_slice());
842
843        // We do not eagerly inline, except if the Vec is empty.
844        assert_eq!(repr.spilled(), !control.is_empty());
845        // The allocation of the Vec should get re-used.
846        assert_eq!(repr.as_ptr() == pointer, !control.is_empty());
847    }
848
849    #[proptest]
850    #[cfg_attr(miri, ignore)]
851    fn proptest_serde(initial: Vec<u8>) {
852        let repr = CompactBytes::new(&initial);
853
854        let (repr_json, ctrl_json) = match (
855            serde_json::to_string(&repr),
856            serde_json::to_string(&initial),
857        ) {
858            (Ok(r), Ok(c)) => (r, c),
859            (Err(_), Err(_)) => return Ok(()),
860            (r, c) => panic!("Got mismatched results when serializing {r:?}, {c:?}"),
861        };
862
863        prop_assert_eq!(&repr_json, &ctrl_json);
864
865        let (repr_rnd_trip, ctrl_rnd_trip): (CompactBytes, Vec<u8>) = match (
866            serde_json::from_str(&repr_json),
867            serde_json::from_str(&ctrl_json),
868        ) {
869            (Ok(r), Ok(c)) => (r, c),
870            (Err(_), Err(_)) => return Ok(()),
871            (r, c) => panic!("Got mismatched results {r:?}, {c:?}"),
872        };
873
874        prop_assert_eq!(&repr, &repr_rnd_trip);
875        prop_assert_eq!(repr_rnd_trip, ctrl_rnd_trip);
876    }
877
878    #[test]
879    fn test_heap_bytes_capacity() {
880        let heap = HeapBytesGrowable::with_capacity(1);
881        drop(heap);
882    }
883}