duat_core/text/
bytes.rs

1use std::{iter::FusedIterator, ops::RangeBounds, str::from_utf8_unchecked};
2
3use gapbuf::GapBuffer;
4use lender::{DoubleEndedLender, ExactSizeLender, Lender, Lending};
5
6use super::{Point, TextRange, records::Records, utf8_char_width};
7
8/// The bytes of a [`Text`], encoded in UTF-8
9///
10/// [`Text`]: super::Text
11#[derive(Default, Clone)]
12pub struct Bytes {
13    buf: GapBuffer<u8>,
14    records: Records<[usize; 3]>,
15}
16
17impl Bytes {
18    /// Returns a new instance of a [`Buffer`]
19    pub(crate) fn new(string: &str) -> Self {
20        let buf = GapBuffer::from_iter(string.bytes());
21
22        let len = buf.len();
23        let chars = unsafe {
24            let (s0, s1) = buf.as_slices();
25            std::str::from_utf8_unchecked(s0).chars().count()
26                + std::str::from_utf8_unchecked(s1).chars().count()
27        };
28        let lines = buf.iter().filter(|b| **b == b'\n').count();
29        Self {
30            buf,
31            records: Records::with_max([len, chars, lines]),
32        }
33    }
34
35    ////////// Querying functions
36
37    /// The [`Point`] at the end of the text
38    pub fn len(&self) -> Point {
39        let [b, c, l] = self.records.max();
40        Point::from_raw(b, c, l)
41    }
42
43    /// Whether or not there are any characters in [`Bytes`]
44    ///
45    /// # Note
46    ///
47    /// This does not check for tags, so with a [`Tag::Ghost`],
48    /// there could actually be a "string" of characters on the
49    /// [`Text`], it just wouldn't be considered real "text".
50    ///
51    /// [`Tag::Ghost`]: super::Ghost
52    /// [`Text`]: super::Text
53    pub fn is_empty(&self) -> bool {
54        self.buf.is_empty()
55    }
56
57    /// The `char` at the [`Point`]'s position
58    pub fn char_at(&self, p: Point) -> Option<char> {
59        if p.byte() >= self.len().byte() {
60            return None;
61        }
62
63        let [s0, s1] = self.strs(..).to_array();
64        if p.byte() < s0.len() {
65            s0[p.byte()..].chars().next()
66        } else {
67            s1[p.byte() - s0.len()..].chars().next()
68        }
69    }
70
71    /// An [`Iterator`] over the bytes in a given [range]
72    ///
73    /// If the range is fully or partially out of bounds, one or both
74    /// of the slices might be empty.
75    ///
76    /// [range]: TextRange
77    pub fn buffers(&self, range: impl TextRange) -> Buffers {
78        let range = range.to_range(self.buf.len());
79        let (s0, s1) = self.buf.range(range).as_slices();
80        Buffers([s0.iter(), s1.iter()])
81    }
82
83    /// An [`Iterator`] over the [`&str`]s of the [`Text`]
84    ///
85    /// # Note
86    ///
87    /// The reason why this function returns two strings is that the
88    /// contents of the text are stored in a [`GapBuffer`], which
89    /// works with two strings.
90    ///
91    /// If you want to iterate over them, you can do the following:
92    ///
93    /// ```rust
94    /// # use duat_core::{text::Point, prelude::*};
95    /// # let (p0, p1) = (Point::default(), Point::default());
96    /// # let text = Text::new();
97    /// let bytes = text.bytes();
98    /// bytes.strs(p0..p1).flat_map(str::chars);
99    /// ```
100    ///
101    /// Do note that you should avoid iterators like [`str::lines`],
102    /// as they will separate the line that is partially owned by each
103    /// [`&str`]:
104    ///
105    /// ```rust
106    /// let broken_up_line = [
107    ///     "This is line 1, business as usual.\nThis is line 2, but it",
108    ///     "is broken into two separate strings.\nSo 4 lines would be counted, instead of 3",
109    /// ];
110    /// ```
111    ///
112    /// # [`TextRange`] behavior:
113    ///
114    /// If you give a single [`usize`]/[`Point`], it will be
115    /// interpreted as a range from.
116    ///
117    /// If you want the two full [`&str`]s, see [`strs`]
118    ///
119    /// [`&str`]: str
120    /// [`Text`]: super::Text
121    /// [range]: TextRange
122    /// [`strs`]: Self::strs
123    pub fn strs(&self, range: impl TextRange) -> Strs {
124        let range = range.to_range(self.buf.len());
125        Strs(self.strs_in_range_inner(range).into_iter())
126    }
127
128    /// Returns an iterator over the lines in a given range
129    ///
130    /// The lines are inclusive, that is, it will iterate over the
131    /// whole line, not just the parts within the range.
132    ///
133    /// [range]: TextRange
134    pub fn lines(&self, range: impl TextRange) -> Lines<'_> {
135        let range = range.to_range(self.len().byte());
136        let start = self.point_at_line(self.point_at(range.start).line());
137        let end = {
138            let end = self.point_at(range.end);
139            let line_start = self.point_at_line(end.line());
140            match line_start == end {
141                true => end,
142                false => self.point_at_line((end.line() + 1).min(self.len().line())),
143            }
144        };
145
146        // If the gap is outside of the range, we can just iterate through it
147        // regularly
148        let (fwd_i, rev_i) = (start.line(), end.line());
149        if let Some(str) = self.get_contiguous(start..end) {
150            let lines = [str.lines(), "".lines()];
151            Lines::new(lines, None, fwd_i, rev_i)
152        // If the gap is within the range, but on a line split, we
153        // can just iterate through two sets of lines.
154        } else if end.byte() > start.byte()
155            && self.buf[self.buf.gap() - 1] != b'\n'
156            && self.buf[self.buf.gap()] != b'\n'
157        {
158            let [str_0, str_1] = self.strs(start..end).to_array();
159            let lines = [str_0.lines(), str_1.lines()];
160            Lines::new(lines, None, fwd_i, rev_i)
161            // Otherwise, the line that was split will need to be
162            // allocated and returned separately.
163        } else {
164            let [str0, str1] = self.strs(start..end).to_array();
165
166            let (before, split0) = match str0.rsplit_once('\n') {
167                Some((before, split)) => (before, split),
168                None => ("", str0),
169            };
170            let (after, split1) = match str1.split_once('\n') {
171                Some((after, split)) => (after, split),
172                None => ("", str1),
173            };
174
175            let lines = [before.lines(), after.lines()];
176            let split_line = Some(split0.to_string() + split1);
177            Lines::new(lines, split_line, fwd_i, rev_i)
178        }
179    }
180
181    /// Returns the two `&str`s in the byte range.
182    fn strs_in_range_inner(&self, range: impl RangeBounds<usize>) -> [&str; 2] {
183        let (s0, s1) = self.buf.as_slices();
184        let (start, end) = crate::get_ends(range, self.buf.len());
185        let (start, end) = (start, end);
186        // Make sure the start and end are in character bounds.
187        assert!(
188            [start, end]
189                .into_iter()
190                .filter_map(|b| self.buf.get(b))
191                .all(|b| utf8_char_width(*b) > 0),
192        );
193
194        unsafe {
195            let r0 = start.min(s0.len())..end.min(s0.len());
196            let r1 = start.saturating_sub(s0.len()).min(s1.len())
197                ..end.saturating_sub(s0.len()).min(s1.len());
198
199            [from_utf8_unchecked(&s0[r0]), from_utf8_unchecked(&s1[r1])]
200        }
201    }
202
203    /// The [`Point`] corresponding to the byte position, 0 indexed
204    ///
205    /// If the byte position would fall in between two characters
206    /// (because the first one comprises more than one byte), the
207    /// first character is chosen as the [`Point`] where the byte is
208    /// located.
209    ///
210    /// # Panics
211    ///
212    /// Will panic if `b` is greater than the length of the text
213    #[inline(always)]
214    pub fn point_at(&self, b: usize) -> Point {
215        assert!(
216            b <= self.len().byte(),
217            "byte out of bounds: the len is {}, but the byte is {b}",
218            self.len().byte()
219        );
220        let [c_b, c_c, mut c_l] = self.records.closest_to(b);
221
222        let found = if b >= c_b {
223            let [s0, s1] = self.strs_in_range_inner(c_b..);
224
225            s0.char_indices()
226                .chain(s1.char_indices().map(|(b, char)| (b + s0.len(), char)))
227                .enumerate()
228                .map(|(i, (this_b, char))| {
229                    c_l += (char == '\n') as usize;
230                    (c_b + this_b, c_c + i, c_l - (char == '\n') as usize)
231                })
232                .take_while(|&(rhs, ..)| b >= rhs)
233                .last()
234        } else {
235            let mut c_len = 0;
236            self.strs_in_range_inner(..c_b)
237                .into_iter()
238                .flat_map(str::chars)
239                .rev()
240                .enumerate()
241                .map(|(i, char)| {
242                    c_l -= (char == '\n') as usize;
243                    c_len += char.len_utf8();
244                    (c_b - c_len, c_c - (i + 1), c_l)
245                })
246                .take_while(|&(rhs, ..)| b <= rhs)
247                .last()
248        };
249
250        found
251            .map(|(b, c, l)| Point::from_raw(b, c, l))
252            .unwrap_or(self.len())
253    }
254
255    /// The [`Point`] associated with the `c`th char
256    ///
257    /// # Panics
258    ///
259    /// Will panic if `c` is greater than the number of chars in the
260    /// text.
261    #[inline(always)]
262    pub fn point_at_char(&self, c: usize) -> Point {
263        assert!(
264            c <= self.len().char(),
265            "char out of bounds: the len is {}, but the char is {c}",
266            self.len().char()
267        );
268        let [c_b, c_c, mut c_l] = self.records.closest_to_by_key(c, |[_, c, _]| *c);
269
270        let found = if c >= c_c {
271            let [s0, s1] = self.strs_in_range_inner(c_b..);
272
273            s0.char_indices()
274                .chain(s1.char_indices().map(|(b, char)| (b + s0.len(), char)))
275                .enumerate()
276                .map(|(i, (this_b, char))| {
277                    c_l += (char == '\n') as usize;
278                    (c_b + this_b, c_c + i, c_l - (char == '\n') as usize)
279                })
280                .take_while(|&(_, rhs, _)| c >= rhs)
281                .last()
282        } else {
283            let mut c_len = 0;
284            self.strs_in_range_inner(..)
285                .into_iter()
286                .flat_map(str::chars)
287                .rev()
288                .enumerate()
289                .map(|(i, char)| {
290                    c_l -= (char == '\n') as usize;
291                    c_len += char.len_utf8();
292                    (c_b - c_len, c_c - (i + 1), c_l)
293                })
294                .take_while(|&(_, rhs, _)| c <= rhs)
295                .last()
296        };
297
298        found
299            .map(|(b, c, l)| Point::from_raw(b, c, l))
300            .unwrap_or(self.len())
301    }
302
303    /// The [`Point`] where the `l`th line starts, 0 indexed
304    ///
305    /// If `l == number_of_lines`, returns the last point of the
306    /// text.
307    ///
308    /// # Panics
309    ///
310    /// Will panic if the number `l` is greater than the number of
311    /// lines on the text
312    #[inline(always)]
313    pub fn point_at_line(&self, l: usize) -> Point {
314        assert!(
315            l <= self.len().line(),
316            "line out of bounds: the len is {}, but the line is {l}",
317            self.len().line()
318        );
319        let (c_b, c_c, mut c_l) = {
320            let [mut b, mut c, l] = self.records.closest_to_by_key(l, |[.., l]| *l);
321            self.strs_in_range_inner(..b)
322                .into_iter()
323                .flat_map(str::chars)
324                .rev()
325                .take_while(|c| *c != '\n')
326                .for_each(|char| {
327                    b -= char.len_utf8();
328                    c -= 1;
329                });
330            (b, c, l)
331        };
332
333        let found = if l >= c_l {
334            let [s0, s1] = self.strs_in_range_inner(c_b..);
335
336            s0.char_indices()
337                .chain(s1.char_indices().map(|(b, char)| (b + s0.len(), char)))
338                .enumerate()
339                .map(|(i, (this_b, char))| {
340                    c_l += (char == '\n') as usize;
341                    (c_b + this_b, c_c + i, c_l - (char == '\n') as usize)
342                })
343                .find(|&(.., rhs)| l == rhs)
344        } else {
345            let mut c_len = 0;
346            self.strs_in_range_inner(..c_b)
347                .into_iter()
348                .flat_map(str::chars)
349                .rev()
350                .enumerate()
351                .map(|(i, char)| {
352                    c_l -= (char == '\n') as usize;
353                    c_len += char.len_utf8();
354                    (c_b - c_len, c_c - (i + 1), c_l)
355                })
356                .take_while(|&(.., rhs)| l <= rhs)
357                .last()
358        };
359
360        found
361            .map(|(b, c, l)| Point::from_raw(b, c, l))
362            .unwrap_or(self.len())
363    }
364
365    /// The start and end [`Point`]s for the `l`th line
366    ///
367    /// If `l == number_of_lines`, these points will be the same.
368    ///
369    /// # Panics
370    ///
371    /// Will panic if the number `l` is greater than the number of
372    /// lines on the text
373    #[inline(always)]
374    pub fn points_of_line(&self, l: usize) -> [Point; 2] {
375        assert!(
376            l <= self.len().line(),
377            "byte out of bounds: the len is {}, but the line is {l}",
378            self.len().line()
379        );
380
381        let start = self.point_at_line(l);
382        let end = self
383            .chars_fwd(start)
384            .find_map(|(p, _)| (p.line() > start.line()).then_some(p))
385            .unwrap_or(start);
386        [start, end]
387    }
388
389    /// The last [`Point`] associated with a `char`
390    ///
391    /// This will give the [`Point`] of the last `char` of the text.
392    /// The difference between this method and [`len`] is that
393    /// it will return a [`Point`] one position earlier than it. If
394    /// the text is completely empty, it will return [`None`].
395    ///
396    /// [`len`]: Self::len
397    pub fn last_point(&self) -> Option<Point> {
398        self.strs(..)
399            .chars()
400            .next_back()
401            .map(|char| self.len().rev(char))
402    }
403
404    /// A forward iterator of the [`char`]s of [`Bytes`]
405    ///
406    /// Each [`char`] will be accompanied by a [`Point`], which is the
407    /// position where said character starts, e.g.
408    /// [`Point::default()`] for the first character
409    pub fn chars_fwd(&self, p: Point) -> impl Iterator<Item = (Point, char)> + '_ {
410        self.strs_in_range_inner(p.byte()..)
411            .into_iter()
412            .flat_map(str::chars)
413            .scan(p, |p, char| {
414                let old_p = *p;
415                *p = p.fwd(char);
416                Some((old_p, char))
417            })
418    }
419
420    /// A reverse iterator of the [`char`]s in [`Bytes`]
421    ///
422    /// Each [`char`] will be accompanied by a [`Point`], which is the
423    /// position where said character starts, e.g.
424    /// [`Point::default()`] for the first character
425    pub fn chars_rev(&self, p: Point) -> impl Iterator<Item = (Point, char)> + '_ {
426        self.strs_in_range_inner(..p.byte())
427            .into_iter()
428            .flat_map(str::chars)
429            .rev()
430            .scan(p, |p, char| {
431                *p = p.rev(char);
432                Some((*p, char))
433            })
434    }
435
436    ////////// Modification functions
437
438    /// Applies a [`Change`] to the [`GapBuffer`] within
439    ///
440    /// [`Change`]: super::Change
441    pub(super) fn apply_change(&mut self, change: super::Change<&str>) {
442        let edit = change.added_text();
443        let start = change.start();
444
445        let new_len = {
446            let lines = edit.bytes().filter(|b| *b == b'\n').count();
447            [edit.len(), edit.chars().count(), lines]
448        };
449
450        let old_len = unsafe {
451            let range = start.byte()..change.taken_end().byte();
452            let str = String::from_utf8_unchecked(
453                self.buf
454                    .splice(range, edit.as_bytes().iter().cloned())
455                    .collect(),
456            );
457
458            let lines = str.bytes().filter(|b| *b == b'\n').count();
459            [str.len(), str.chars().count(), lines]
460        };
461
462        let start_rec = [start.byte(), start.char(), start.line()];
463        self.records.transform(start_rec, old_len, new_len);
464        self.records.insert(start_rec);
465    }
466
467    /// Extends this [`Bytes`] with another
468    pub(super) fn extend(&mut self, other: Self) {
469        self.buf.extend(other.buf);
470        self.records
471            .transform(self.records.max(), [0, 0, 0], other.records.max())
472    }
473
474    /// Adds a record in the given position
475    pub(super) fn add_record(&mut self, [b, c, l]: [usize; 3]) {
476        self.records.insert([b, c, l]);
477    }
478
479    ////////// One str functions
480
481    /// Gets a single [`&str`] from a given [range]
482    ///
483    /// This is the equivalent of calling
484    /// [`Bytes::make_contiguous`] and [`Bytes::get_contiguous`].
485    /// While this takes leas space in code, calling the other two
486    /// functions means that you won't be mutably borrowing the
487    /// [`Bytes`] anymore, so if that matters to you, you should do
488    /// that.
489    ///
490    /// [`&str`]: str
491    /// [range]: TextRange
492    pub fn contiguous(&mut self, range: impl TextRange) -> &str {
493        self.make_contiguous(range.clone());
494        self.get_contiguous(range).unwrap()
495    }
496
497    /// Moves the [`GapBuffer`]'s gap, so that the `range` is whole
498    ///
499    /// The return value is the value of the gap, if the second `&str`
500    /// is the contiguous one.
501    pub fn make_contiguous(&mut self, range: impl TextRange) {
502        let range = range.to_range(self.len().byte());
503        let gap = self.buf.gap();
504
505        if range.end <= gap || range.start >= gap {
506            return;
507        }
508
509        if gap.abs_diff(range.start) < gap.abs_diff(range.end) {
510            self.buf.set_gap(range.start);
511        } else {
512            self.buf.set_gap(range.end);
513        }
514    }
515
516    /// Tries to get a contiguous [`&str`] from the [`Bytes`]
517    ///
518    /// Returns [`None`] if the gap of the inner buffer was within the
519    /// given range.
520    ///
521    /// You can ensure that the gap is outside of that range by
522    /// calling [`make_contiguous`], although that requires a mutable
523    /// reference.
524    ///
525    /// [`&str`]: str
526    /// [`make_contiguous`]: Self::make_contiguous
527    pub fn get_contiguous(&self, range: impl TextRange) -> Option<&str> {
528        let range = range.to_range(self.len().byte());
529        let [s0, s1] = self.strs(..).to_array();
530
531        if range.end <= self.buf.gap() {
532            s0.get(range)
533        } else {
534            let gap = self.buf.gap();
535            s1.get(range.start - gap..range.end - gap)
536        }
537    }
538}
539
540/// A [`Lender`] over the lines on [`Bytes`]
541///
542/// The reason for this being a [`Lender`], rather than a regular
543/// [`Iterator`] is because the [`Bytes`] use a [`GapBuffer`] within,
544/// which means that any line may be split in two. In order to still
545/// return it as an `&str`, a new [`String`] needs to be allocated,
546/// which will be owned by the [`Lines`], hence the [`Lender`] trait.
547pub struct Lines<'a> {
548    lines: [std::str::Lines<'a>; 2],
549    split_line: Option<String>,
550    fwd_i: usize,
551    rev_i: usize,
552    split_line_used: bool,
553}
554
555impl<'a> Lines<'a> {
556    fn new(
557        lines: [std::str::Lines<'a>; 2],
558        split_line: Option<String>,
559        fwd_i: usize,
560        rev_i: usize,
561    ) -> Self {
562        Self {
563            lines,
564            split_line,
565            fwd_i,
566            rev_i,
567            split_line_used: false,
568        }
569    }
570}
571
572impl<'a, 'text> Lending<'a> for Lines<'text> {
573    type Lend = (usize, &'a str);
574}
575
576impl<'a> Lender for Lines<'a> {
577    fn next(&mut self) -> Option<lender::Lend<'_, Self>> {
578        self.lines[0]
579            .next()
580            .or_else(|| {
581                if self.split_line_used {
582                    None
583                } else {
584                    self.split_line_used = true;
585                    self.split_line.as_deref()
586                }
587            })
588            .or_else(|| self.lines[1].next())
589            .map(|line| {
590                self.fwd_i += 1;
591                (self.fwd_i - 1, line)
592            })
593    }
594
595    fn size_hint(&self) -> (usize, Option<usize>) {
596        (self.rev_i - self.fwd_i, Some(self.rev_i - self.fwd_i))
597    }
598}
599
600impl<'a> DoubleEndedLender for Lines<'a> {
601    fn next_back(&mut self) -> Option<lender::Lend<'_, Self>> {
602        self.lines[1]
603            .next_back()
604            .or_else(|| {
605                if self.split_line_used {
606                    None
607                } else {
608                    self.split_line_used = true;
609                    self.split_line.as_deref()
610                }
611            })
612            .or_else(|| self.lines[0].next_back())
613            .map(|line| {
614                self.rev_i -= 1;
615                (self.rev_i, line)
616            })
617    }
618}
619
620impl<'a> ExactSizeLender for Lines<'a> {}
621
622/// An [`Iterator`] over the bytes in a [`Text`]
623///
624/// [`Text`]: super::Text
625#[derive(Clone)]
626pub struct Buffers<'a>([std::slice::Iter<'a, u8>; 2]);
627
628impl<'a> Buffers<'a> {
629    /// Converts this [`Iterator`] into an array of its two parts
630    pub fn to_array(&self) -> [&'a [u8]; 2] {
631        self.0.clone().map(|iter| iter.as_slice())
632    }
633}
634
635impl<'a> Iterator for Buffers<'a> {
636    type Item = u8;
637
638    fn next(&mut self) -> Option<Self::Item> {
639        self.0[0].next().or_else(|| self.0[1].next()).copied()
640    }
641
642    fn size_hint(&self) -> (usize, Option<usize>) {
643        let (l0, u0) = self.0[0].size_hint();
644        let (l1, u1) = self.0[1].size_hint();
645        (l0 + l1, Some(u0.unwrap() + u1.unwrap()))
646    }
647}
648
649impl<'a> ExactSizeIterator for Buffers<'a> {}
650
651impl<'a> DoubleEndedIterator for Buffers<'a> {
652    fn next_back(&mut self) -> Option<Self::Item> {
653        self.0[1]
654            .next_back()
655            .or_else(|| self.0[0].next_back())
656            .copied()
657    }
658}
659
660/// An [`Iterator`] over the [`&str`]s in a [`Text`]
661///
662/// [`&str`]: str
663/// [`Text`]: super::Text
664#[derive(Clone)]
665pub struct Strs<'a>(std::array::IntoIter<&'a str, 2>);
666
667impl<'a> Strs<'a> {
668    /// Converts this [`Iterator`] into an array of its two parts
669    pub fn to_array(&self) -> [&'a str; 2] {
670        let strs = self.0.as_slice();
671        [
672            strs.first().copied().unwrap_or(""),
673            strs.last().copied().unwrap_or(""),
674        ]
675    }
676
677    /// Iterates over the [`char`]s of both [`&str`]s
678    ///
679    /// [`&str`]: str
680    pub fn chars(self) -> impl DoubleEndedIterator<Item = char> + 'a {
681        let [s0, s1] = self.to_array();
682        s0.chars().chain(s1.chars())
683    }
684}
685
686impl<'a> Iterator for Strs<'a> {
687    type Item = &'a str;
688
689    fn next(&mut self) -> Option<Self::Item> {
690        self.0.next()
691    }
692
693    fn size_hint(&self) -> (usize, Option<usize>) {
694        self.0.size_hint()
695    }
696}
697
698impl ExactSizeIterator for Strs<'_> {}
699
700impl DoubleEndedIterator for Strs<'_> {
701    fn next_back(&mut self) -> Option<Self::Item> {
702        self.0.next_back()
703    }
704}
705
706impl FusedIterator for Strs<'_> {}
707
708impl std::fmt::Display for Strs<'_> {
709    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
710        let [s0, s1] = self.to_array();
711        write!(f, "{s0}{s1}")
712    }
713}
714
715impl std::fmt::Debug for Bytes {
716    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
717        f.debug_struct("Bytes")
718            .field("buf", &self.strs(..).to_array())
719            .field("records", &self.records)
720            .finish()
721    }
722}
723
724impl PartialEq for Bytes {
725    fn eq(&self, other: &Self) -> bool {
726        self.buf.as_slices() == other.buf.as_slices()
727    }
728}
729
730impl PartialEq<&str> for Bytes {
731    fn eq(&self, other: &&str) -> bool {
732        let [s0, s1] = self.strs(..).to_array();
733        other.len() == s0.len() + s1.len() && &other[..s0.len()] == s0 && &other[s0.len()..] == s1
734    }
735}
736
737impl PartialEq<String> for Bytes {
738    fn eq(&self, other: &String) -> bool {
739        let [s0, s1] = self.strs(..).to_array();
740        other.len() == s0.len() + s1.len() && &other[..s0.len()] == s0 && &other[s0.len()..] == s1
741    }
742}