Skip to main content

seq_str/
seq_bytes.rs

1use alloc::vec::Vec;
2use core::fmt;
3
4/// A sequence of `&[u8]`, stored contiguously
5///
6/// This can be used as a drop-in replacement for `Vec<Vec<u8>>` in some cases,
7/// with better memory locality and fewer memory allocations.
8///
9/// When using `SeqBytes` instead of `Vec<Vec<u8>>`, the individual byte strings
10/// cannot be resized, but when this isn't needed there isn't much downside otherwise.
11///
12/// The container also supports "emplace"-style APIs like `in_place_writer`, which allow you to
13/// write the next element directly into the contiguous buffer with minimal overhead.
14#[derive(Clone, Default, Eq, PartialEq, Hash)]
15pub struct SeqBytes {
16    data: Vec<u8>,
17    offsets: Vec<usize>,
18}
19
20impl SeqBytes {
21    /// Create a new SeqBytes
22    pub fn new() -> Self {
23        Self::default()
24    }
25
26    /// Check if the sequence is empty
27    pub fn is_empty(&self) -> bool {
28        self.len() == 0
29    }
30
31    /// Get the number of slices in the sequence
32    pub fn len(&self) -> usize {
33        self.offsets.len()
34    }
35
36    /// Reserve capacity for more slices
37    pub fn reserve(&mut self, extra: usize) {
38        self.offsets.reserve(extra);
39        // Guess how much data to reserve based on existing usage
40        if !self.is_empty() && extra > 0 {
41            let a = extra * 4;
42            let b = self.data.len() * 4;
43            // estimate data.len * extra / offsets.len
44            let c = (self.data.len() * extra)
45                >> ((usize::BITS - 1) - self.offsets.len().leading_zeros());
46
47            #[allow(clippy::collapsible_else_if)]
48            let median = if a <= b {
49                if a <= c { core::cmp::min(b, c) } else { a }
50            } else {
51                if a <= c { a } else { core::cmp::max(b, c) }
52            };
53
54            self.data.reserve(median);
55        } else {
56            self.data.reserve(4 * extra);
57        }
58    }
59
60    /// Shrink container to fit the current data
61    pub fn shrink_to_fit(&mut self) {
62        self.data.shrink_to_fit();
63        self.offsets.shrink_to_fit();
64    }
65
66    /// Get the sum of the lengths of the byte strings
67    pub fn num_bytes(&self) -> usize {
68        self.data.len()
69    }
70
71    /// Get the i'th element of the sequence in a checked manner
72    pub fn get(&self, idx: usize) -> Option<&[u8]> {
73        let first = self.offsets.get(idx)?;
74        match self.offsets.get(idx + 1) {
75            Some(second) => Some(&self.data[*first..*second]),
76            None => Some(&self.data[*first..]),
77        }
78    }
79
80    /// Get the i'th element of the sequence in a checked manner
81    pub fn get_mut(&mut self, idx: usize) -> Option<&mut [u8]> {
82        let first = self.offsets.get(idx)?;
83        match self.offsets.get(idx + 1) {
84            Some(second) => Some(&mut self.data[*first..*second]),
85            None => Some(&mut self.data[*first..]),
86        }
87    }
88
89    /// Check if the sequence contains a particular element
90    pub fn contains(&self, s: impl AsRef<[u8]>) -> bool {
91        let s = s.as_ref();
92        self.iter().any(|b| b == s)
93    }
94
95    /// Push a &[u8] onto the sequence
96    pub fn push(&mut self, s: impl AsRef<[u8]>) {
97        self.offsets.push(self.data.len());
98        self.data.extend(s.as_ref().iter());
99    }
100
101    /// Get the last &[u8] of the sequence
102    pub fn last(&self) -> Option<&[u8]> {
103        match self.offsets.last() {
104            Some(o) => Some(&self.data[*o..]),
105            None => None,
106        }
107    }
108
109    /// Pop the last element of the container
110    /// Note that we can't return it because of lifetimes, so call [last] before popping.
111    pub fn pop(&mut self) {
112        if let Some(o) = self.offsets.pop() {
113            self.data.truncate(o);
114        }
115    }
116
117    /// Iterate over the sequence of &[u8]
118    pub fn iter(&self) -> SeqBytesIter<'_> // impl ExactSizeIterator<Item = &[u8]>
119    {
120        SeqBytesIter {
121            data: &self.data[..],
122            offsets: &self.offsets[..],
123        }
124    }
125
126    /// Iterate over the sequence of &mut [u8]
127    pub fn iter_mut(&mut self) -> SeqBytesIterMut<'_> // impl ExactSizeIterator<Item = &mut[u8]>
128    {
129        SeqBytesIterMut {
130            data: &mut self.data[..],
131            offsets: &self.offsets[..],
132        }
133    }
134
135    /// Iterate over the sequence of &[u8], in chunks of given size.
136    /// Returns an iterator which yields one iterator for each chunk.
137    /// The last chunk may be smaller.
138    pub fn chunks(&self, chunk_size: usize) -> SeqBytesChunksIter<'_> {
139        SeqBytesChunksIter {
140            chunk_size,
141            iter: self.iter(),
142        }
143    }
144
145    // Helper to convert range bounds object to a range
146    fn range_bounds_to_range(
147        &self,
148        range_bounds: impl core::ops::RangeBounds<usize>,
149    ) -> (usize, usize) {
150        use core::ops::Bound;
151
152        let start_idx = match range_bounds.start_bound() {
153            Bound::Included(s) => *s,
154            Bound::Excluded(s) => s + 1,
155            Bound::Unbounded => 0,
156        };
157
158        let end_idx = match range_bounds.end_bound() {
159            Bound::Included(e) => e + 1,
160            Bound::Excluded(e) => *e,
161            Bound::Unbounded => self.offsets.len(),
162        };
163
164        (start_idx, end_idx)
165    }
166
167    /// Iterate over a range of the sequence of `&[u8]`
168    ///
169    /// This resembles [std::collections::BTreeMap::range], and is needed becuase like `BTreeMap`,
170    /// we can't implement `Deref` or `SliceIndex<Range>` and produce a slice of our contents.
171    /// See also [as_vec].
172    pub fn range(&self, range_bounds: impl core::ops::RangeBounds<usize>) -> SeqBytesIter<'_> {
173        let (start_idx, end_idx) = self.range_bounds_to_range(range_bounds);
174        let data_end = self
175            .offsets
176            .get(end_idx)
177            .cloned()
178            .unwrap_or(self.data.len());
179
180        SeqBytesIter {
181            data: &self.data[0..data_end],
182            offsets: &self.offsets[start_idx..end_idx],
183        }
184    }
185
186    /// Iterate over a range of the sequence of `&mut [u8]`
187    pub fn range_mut(
188        &mut self,
189        range_bounds: impl core::ops::RangeBounds<usize>,
190    ) -> SeqBytesIterMut<'_> {
191        let (start_idx, end_idx) = self.range_bounds_to_range(range_bounds);
192        let data_end = self
193            .offsets
194            .get(end_idx)
195            .cloned()
196            .unwrap_or(self.data.len());
197
198        SeqBytesIterMut {
199            data: &mut self.data[0..data_end],
200            offsets: &self.offsets[start_idx..end_idx],
201        }
202    }
203
204    /// Truncate to at most the first n slices
205    pub fn truncate(&mut self, new_size: usize) {
206        if let Some(off) = self.offsets.get(new_size) {
207            self.data.truncate(*off);
208            self.offsets.truncate(new_size);
209        }
210    }
211
212    /// Resize to contain only the first n `&[u8]`, or pad up to n slices, with empty slices added
213    pub fn resize(&mut self, new_size: usize) {
214        if let Some(off) = self.offsets.get(new_size) {
215            self.data.truncate(*off);
216            self.offsets.truncate(new_size);
217        } else {
218            // Push empty slices until offsets has length new_size
219            let d = new_size - self.offsets.len();
220            self.offsets.reserve(d);
221            for _ in 0..d {
222                self.offsets.push(self.data.len());
223            }
224        }
225    }
226
227    /// Retain only those slices satisfying a predicate.
228    /// The slices are always visited in order, similar to [std::vec::Vec::retain].
229    pub fn retain(&mut self, mut pred: impl FnMut(&[u8]) -> bool) {
230        self.retain_mut(|elem| pred(elem))
231    }
232
233    /// Retain only those slices satisfying a predicate.
234    /// The slices are always visited in order, similar to [std::vec::Vec::retain_mut].
235    pub fn retain_mut(&mut self, mut pred: impl FnMut(&mut [u8]) -> bool) {
236        let (data, offsets) = (&mut self.data, &mut self.offsets);
237
238        let mut offset_write_idx = 0;
239        let mut kept_bytes = 0;
240
241        // Invariant:
242        // Offsets is not reduced in length during this loop
243        // Data is not reduced in length during this loop
244        for offset_idx in 0..offsets.len() {
245            let start = offsets[offset_idx];
246            let end = offsets.get(offset_idx + 1).cloned().unwrap_or(data.len());
247
248            let outcome = pred(&mut data[start..end]);
249            if outcome {
250                // We will preserve kept_len additional bytes,
251                // write their starting offset first.
252                let kept_len = end - start;
253                offsets[offset_write_idx] = kept_bytes;
254                offset_write_idx += 1;
255
256                // Move from data[start..end] to
257                // data[kept_bytes, kept_bytes + kept_len]
258                // if start == kept bytes we don't have to do anything
259                if kept_bytes != start {
260                    for byte_idx in 0..kept_len {
261                        data[kept_bytes + byte_idx] = data[start + byte_idx];
262                    }
263                }
264                kept_bytes += kept_len;
265            }
266        }
267        drop(pred);
268
269        // Truncate both offsets and data to what was actually retained
270        offsets.truncate(offset_write_idx);
271        data.truncate(kept_bytes);
272    }
273
274    /// Get an `impl std::io::Write` which can be used to write the next slice
275    /// directly into the buffer without copying.
276    #[cfg(feature = "std")]
277    pub fn in_place_writer(&mut self) -> impl std::io::Write {
278        // Correctness:
279        // If we push a new offset on, then we have conceptually added a new string.
280        // If the only thing that happens after that is that data is appended to self.data,
281        // then the final state is correct and offsets doesn't need further adjusting.
282        //
283        // The only thing they can do with std::io::Write is push more bytes.
284        // And no other changes can be made to SeqBytes until that writer is dropped,
285        // because it captures &mut self.
286        //
287        // So we don't need to return an object with a Drop impl, we will always end
288        // up in the correct state.
289        self.offsets.push(self.data.len());
290        &mut self.data
291    }
292
293    /// Version of `in_place_writer` that doesn't require `std::io::Write` trait
294    ///
295    /// Any bytes passed to the result of this function get concatenated to produce the
296    /// newest byte string in the sequence. The new item is final when the writer is dropped.
297    pub fn in_place_writer_no_std(&mut self) -> impl FnMut(&[u8]) {
298        self.offsets.push(self.data.len());
299
300        let dat = &mut self.data;
301
302        move |b: &[u8]| {
303            dat.extend(b);
304        }
305    }
306
307    /// Express as a Vec<&[u8]>. The main reason that this may be useful is that there are
308    /// useful methods on slice types `&[&[u8]]`, for example, [core::slice::binary_search],
309    /// but `SeqBytes` itself doesn't implement `Deref` the way that `Vec` does and can
310    /// only produce such a slice by allocating.
311    ///
312    /// Note: The trade-offs are that we would need more memory and `in_place_writer` would have
313    /// to be more complicated and slower if we wanted our internal representation of the offsets
314    /// to be a `Vec<&[u8]>`, which would allow such a `Deref` implementation.
315    /// The direction we've taken is to add useful functions from `Vec` and slice
316    /// as needed directly to this type instead, if they can't be obtained in a simpler way.
317    pub fn as_vec(&self) -> Vec<&[u8]> {
318        self.iter().collect()
319    }
320
321    /// Concatenate the `&[u8]` in the sequence into one `&[u8]`
322    pub fn concat(&self) -> &[u8] {
323        &self.data[..]
324    }
325}
326
327impl core::ops::Index<usize> for SeqBytes {
328    type Output = [u8];
329
330    fn index(&self, index: usize) -> &[u8] {
331        let first = self.offsets[index];
332        match self.offsets.get(index + 1) {
333            Some(second) => &self.data[first..*second],
334            None => &self.data[first..],
335        }
336    }
337}
338
339impl core::ops::IndexMut<usize> for SeqBytes {
340    fn index_mut(&mut self, index: usize) -> &mut [u8] {
341        let first = self.offsets[index];
342        match self.offsets.get(index + 1) {
343            Some(second) => &mut self.data[first..*second],
344            None => &mut self.data[first..],
345        }
346    }
347}
348
349impl fmt::Debug for SeqBytes {
350    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
351        f.debug_list().entries(self.iter()).finish()
352    }
353}
354
355/// An iterator over a SeqBytes object
356#[derive(Clone)]
357pub struct SeqBytesIter<'a> {
358    data: &'a [u8],
359    offsets: &'a [usize],
360}
361
362impl<'a> Iterator for SeqBytesIter<'a> {
363    type Item = &'a [u8];
364
365    fn next(&mut self) -> Option<&'a [u8]> {
366        let first = self.offsets.first()?;
367        self.offsets = &self.offsets[1..];
368
369        let second = self.offsets.first().cloned().unwrap_or(self.data.len());
370        Some(&self.data[*first..second])
371    }
372
373    fn size_hint(&self) -> (usize, Option<usize>) {
374        let remaining = self.offsets.len();
375        (remaining, Some(remaining))
376    }
377}
378
379impl<'a> ExactSizeIterator for SeqBytesIter<'a> {}
380
381impl<'a> DoubleEndedIterator for SeqBytesIter<'a> {
382    fn next_back(&mut self) -> Option<&'a [u8]> {
383        let last = *self.offsets.last()?;
384        self.offsets = &self.offsets[..self.offsets.len() - 1];
385
386        let (left, right) = self.data.split_at(last);
387        self.data = left;
388
389        Some(right)
390    }
391}
392
393/// A mutable iterator over a SeqBytes object
394pub struct SeqBytesIterMut<'a> {
395    data: &'a mut [u8],
396    offsets: &'a [usize],
397}
398
399impl<'a> Iterator for SeqBytesIterMut<'a> {
400    type Item = &'a mut [u8];
401
402    fn next(&mut self) -> Option<&'a mut [u8]> {
403        let first = self.offsets.first()?;
404        self.offsets = &self.offsets[1..];
405
406        let second = self.offsets.first().cloned().unwrap_or(self.data.len());
407
408        // Some(&mut self.data[*first..second])
409        // Work around borrow checker issue
410        let slice = unsafe {
411            let start = self.data.as_mut_ptr().add(*first);
412            core::slice::from_raw_parts_mut(start, second - first)
413        };
414
415        Some(slice)
416    }
417
418    fn size_hint(&self) -> (usize, Option<usize>) {
419        let remaining = self.offsets.len();
420        (remaining, Some(remaining))
421    }
422}
423
424impl<'a> ExactSizeIterator for SeqBytesIterMut<'a> {}
425
426impl<'a> DoubleEndedIterator for SeqBytesIterMut<'a> {
427    fn next_back(&mut self) -> Option<&'a mut [u8]> {
428        let last = *self.offsets.last()?;
429        self.offsets = &self.offsets[..self.offsets.len() - 1];
430
431        let (left, right) = self.data.split_at_mut(last);
432        // Work around borrow checker issue
433        self.data = unsafe {
434            let len = left.len();
435            let ptr = left.as_mut_ptr();
436            core::slice::from_raw_parts_mut(ptr, len)
437        };
438
439        // Work around borrow checker issue
440        let slice = unsafe {
441            let len = right.len();
442            let ptr = right.as_mut_ptr();
443            core::slice::from_raw_parts_mut(ptr, len)
444        };
445
446        Some(slice)
447    }
448}
449
450impl<A: AsRef<[u8]>> Extend<A> for SeqBytes {
451    fn extend<T>(&mut self, iter: T)
452    where
453        T: IntoIterator<Item = A>,
454    {
455        let iter = iter.into_iter();
456        self.reserve(iter.size_hint().0);
457        for item in iter {
458            self.push(item);
459        }
460    }
461}
462
463impl<A: AsRef<[u8]>> FromIterator<A> for SeqBytes {
464    // Required method
465    fn from_iter<T>(iter: T) -> Self
466    where
467        T: IntoIterator<Item = A>,
468    {
469        let mut result = SeqBytes::default();
470        result.extend(iter);
471        result
472    }
473}
474
475// IntoIterator can only be implemented for &'a SeqBytes,
476// otherwise the buffer doesn't live long enough.
477impl<'a> IntoIterator for &'a SeqBytes {
478    type Item = &'a [u8];
479    type IntoIter = SeqBytesIter<'a>;
480
481    fn into_iter(self) -> Self::IntoIter {
482        self.iter()
483    }
484}
485
486// A chunks iterator produces SeqBytesIter of a few items at a time
487#[derive(Clone)]
488pub struct SeqBytesChunksIter<'a> {
489    chunk_size: usize,
490    iter: SeqBytesIter<'a>,
491}
492
493impl<'a> Iterator for SeqBytesChunksIter<'a> {
494    type Item = SeqBytesIter<'a>;
495
496    fn next(&mut self) -> Option<Self::Item> {
497        if self.iter.offsets.is_empty() {
498            return None;
499        }
500        if self.iter.offsets.len() <= self.chunk_size {
501            let iter = self.iter.clone();
502            self.iter.offsets = &[];
503            return Some(iter);
504        }
505
506        let (left, right) = self.iter.offsets.split_at(self.chunk_size);
507
508        let data_end = right.first().copied().unwrap_or(self.iter.data.len());
509
510        self.iter.offsets = right;
511
512        Some(SeqBytesIter {
513            data: &self.iter.data[..data_end],
514            offsets: left,
515        })
516    }
517
518    fn size_hint(&self) -> (usize, Option<usize>) {
519        let remaining = self.iter.offsets.len().div_ceil(self.chunk_size);
520        (remaining, Some(remaining))
521    }
522}
523
524impl<'a> ExactSizeIterator for SeqBytesChunksIter<'a> {}
525
526#[cfg(test)]
527mod tests {
528    use super::*;
529    use alloc::{borrow::ToOwned, vec};
530
531    #[test]
532    fn vec_slice_conversions() {
533        let vec_b = vec![b"1", b"2", b"3"];
534
535        let seq_b: SeqBytes = vec_b.into_iter().collect();
536
537        assert_eq!(seq_b.len(), 3);
538        assert_eq!(&seq_b[0], b"1");
539        assert_eq!(&seq_b[1], b"2");
540        assert_eq!(&seq_b[2], b"3");
541
542        let vec_str2 = seq_b.iter().map(ToOwned::to_owned).collect::<Vec<_>>();
543
544        assert_eq!(vec_str2.len(), 3);
545        assert_eq!(vec_str2[0], b"1");
546        assert_eq!(vec_str2[1], b"2");
547        assert_eq!(vec_str2[2], b"3");
548    }
549
550    #[test]
551    fn vec_vec_b_conversions() {
552        let vec_string = vec![b"1".to_owned(), b"2".to_owned(), b"3".to_owned()];
553
554        let seq_b: SeqBytes = vec_string.into_iter().collect();
555
556        assert_eq!(seq_b.len(), 3);
557        assert_eq!(&seq_b[0], b"1");
558        assert_eq!(&seq_b[1], b"2");
559        assert_eq!(&seq_b[2], b"3");
560
561        let vec_str2 = seq_b.iter().map(ToOwned::to_owned).collect::<Vec<_>>();
562
563        assert_eq!(vec_str2.len(), 3);
564        assert_eq!(vec_str2[0], b"1");
565        assert_eq!(vec_str2[1], b"2");
566        assert_eq!(vec_str2[2], b"3");
567    }
568
569    #[test]
570    fn iter_rev() {
571        let vec_b = vec![b"1", b"2", b"3"];
572
573        let seq_b: SeqBytes = vec_b.into_iter().collect();
574
575        assert_eq!(seq_b.len(), 3);
576        assert_eq!(&seq_b[0], b"1");
577        assert_eq!(&seq_b[1], b"2");
578        assert_eq!(&seq_b[2], b"3");
579
580        let seq_b2: SeqBytes = seq_b.into_iter().rev().collect();
581
582        assert_eq!(seq_b2.len(), 3);
583        assert_eq!(&seq_b2[0], b"3");
584        assert_eq!(&seq_b2[1], b"2");
585        assert_eq!(&seq_b2[2], b"1");
586    }
587
588    #[test]
589    fn contains() {
590        let vec_b: Vec<&[u8]> = vec![b"123", b"45", b"6", b"", b"7", b"89"];
591        let seq_b: SeqBytes = vec_b.iter().collect();
592
593        assert!(seq_b.contains(b"123"));
594        assert!(!seq_b.contains(b"12"));
595        assert!(!seq_b.contains(b"1"));
596        assert!(seq_b.contains(b""));
597        assert!(seq_b.contains(b"6"));
598    }
599
600    #[test]
601    fn iter_mut() {
602        let vec_b: Vec<&[u8]> = vec![b"123", b"45", b"6", b"", b"7", b"89"];
603        let mut seq_b: SeqBytes = vec_b.iter().collect();
604
605        for b in seq_b.iter_mut() {
606            if b.len() > 0 {
607                b[0] = b"a"[0];
608            }
609        }
610
611        assert_eq!(seq_b.len(), 6);
612        assert_eq!(&seq_b[0], b"a23");
613        assert_eq!(&seq_b[1], b"a5");
614        assert_eq!(&seq_b[2], b"a");
615        assert_eq!(&seq_b[3], b"");
616        assert_eq!(&seq_b[4], b"a");
617        assert_eq!(&seq_b[5], b"a9");
618    }
619
620    #[test]
621    fn retain() {
622        let vec_b: Vec<&[u8]> = vec![b"123", b"45", b"6", b"", b"7", b"89"];
623        let mut seq_b: SeqBytes = vec_b.iter().collect();
624
625        assert_eq!(seq_b.len(), 6);
626        assert_eq!(&seq_b[0], b"123");
627        assert_eq!(&seq_b[1], b"45");
628        assert_eq!(&seq_b[2], b"6");
629        assert_eq!(&seq_b[3], b"");
630        assert_eq!(&seq_b[4], b"7");
631        assert_eq!(&seq_b[5], b"89");
632
633        seq_b.retain(|b| !b.is_empty());
634
635        assert_eq!(seq_b.len(), 5);
636        assert_eq!(&seq_b[0], b"123");
637        assert_eq!(&seq_b[1], b"45");
638        assert_eq!(&seq_b[2], b"6");
639        assert_eq!(&seq_b[3], b"7");
640        assert_eq!(&seq_b[4], b"89");
641
642        seq_b.retain(|b| b.len() >= 2);
643
644        assert_eq!(seq_b.len(), 3);
645        assert_eq!(&seq_b[0], b"123");
646        assert_eq!(&seq_b[1], b"45");
647        assert_eq!(&seq_b[2], b"89");
648
649        seq_b.retain(|b| b.len() <= 2);
650
651        assert_eq!(seq_b.len(), 2);
652        assert_eq!(&seq_b[0], b"45");
653        assert_eq!(&seq_b[1], b"89");
654
655        seq_b.push(b"123");
656
657        assert_eq!(seq_b.len(), 3);
658        assert_eq!(&seq_b[0], b"45");
659        assert_eq!(&seq_b[1], b"89");
660        assert_eq!(&seq_b[2], b"123");
661
662        seq_b.retain(|b| b.len() >= 3);
663
664        assert_eq!(seq_b.len(), 1);
665        assert_eq!(&seq_b[0], b"123");
666
667        seq_b.resize(3);
668
669        assert_eq!(seq_b.len(), 3);
670        assert_eq!(&seq_b[0], b"123");
671        assert_eq!(&seq_b[1], b"");
672        assert_eq!(&seq_b[2], b"");
673
674        seq_b.truncate(5);
675
676        assert_eq!(seq_b.len(), 3);
677        assert_eq!(&seq_b[0], b"123");
678        assert_eq!(&seq_b[1], b"");
679        assert_eq!(&seq_b[2], b"");
680
681        seq_b.truncate(2);
682
683        assert_eq!(seq_b.len(), 2);
684        assert_eq!(&seq_b[0], b"123");
685        assert_eq!(&seq_b[1], b"");
686    }
687
688    #[test]
689    fn chunks() {
690        let vec_b: Vec<&[u8]> = vec![b"123", b"45", b"6", b"", b"7", b"89"];
691        let seq_b: SeqBytes = vec_b.iter().collect();
692
693        let chunked: Vec<Vec<&[u8]>> = seq_b.chunks(2).map(|chunk| chunk.collect()).collect();
694
695        assert_eq!(chunked.len(), 3);
696        assert_eq!(chunked[0].len(), 2);
697        assert_eq!(chunked[0][0], b"123");
698        assert_eq!(chunked[0][1], b"45");
699        assert_eq!(chunked[1].len(), 2);
700        assert_eq!(chunked[1][0], b"6");
701        assert_eq!(chunked[1][1], b"");
702        assert_eq!(chunked[2].len(), 2);
703        assert_eq!(chunked[2][0], b"7");
704        assert_eq!(chunked[2][1], b"89");
705
706        let chunked: Vec<Vec<&[u8]>> = seq_b.chunks(4).map(|chunk| chunk.collect()).collect();
707
708        assert_eq!(chunked.len(), 2);
709        assert_eq!(chunked[0].len(), 4);
710        assert_eq!(chunked[0][0], b"123");
711        assert_eq!(chunked[0][1], b"45");
712        assert_eq!(chunked[0][2], b"6");
713        assert_eq!(chunked[0][3], b"");
714        assert_eq!(chunked[1].len(), 2);
715        assert_eq!(chunked[1][0], b"7");
716        assert_eq!(chunked[1][1], b"89");
717    }
718}