string_view/
lib.rs

1//! ## String View
2//!
3//! #### Work with views into string slices. Safely extend, reduce without losing parent string size.
4//!
5//! #### Use in-place modifications to speed up your code.
6//!
7//! Example:
8//!
9//! ```rust
10//! let program_text = r#"
11//! fn main() {
12//!     let text = "Hello World";
13//! }
14//! "#;
15//!
16//! use string_view::StrExt;
17//!
18//! let mut view = program_text.view_part(0, 0);
19//! view.extend_while(|ch| ch == ' ' || ch == '\n');
20//! view.extend_while(char::is_alphabetic);
21//! view.reduce_left_while(|ch| ch == ' ' || ch == '\n');
22//! assert_eq!(view.as_str(), "fn");
23//!
24//! view.try_extend(1).unwrap();
25//! view.extend_while(char::is_alphabetic);
26//! view.try_extend(2).unwrap();
27//! assert_eq!(view.as_str(), "fn main()");
28//!
29//! view.extend_while(|ch| ch == ' ' || ch == '\n' || ch == '{');
30//! view.shrink_to_end();
31//! view.extend_while(|_| true);
32//! view.reduce_while(|ch| ch == ' ' || ch == '\n' || ch == '}');
33//! assert_eq!(view.as_str(), r#"let text = "Hello World";"#);
34//! 
35//! view.reduce_while(|ch| ch == ';');
36//! view.reduce(1);
37//! view.shrink_to_end();
38//! view.extend_left_while(|ch| ch != '"');
39//! assert_eq!(view.as_str(), "Hello World");
40//! ```
41
42#![no_std]
43
44use core::error::Error;
45use core::fmt::{Debug, Display};
46use core::ops::{Deref, DerefMut};
47
48pub struct StringView<'a> {
49    pub base: &'a str,
50    /// byte idx of view start inside [`StringView::base`].
51    view_start: usize,
52    /// byte len of view inside [`StringView::base`].
53    view_len: usize,
54}
55
56impl<'a> StringView<'a> {
57    /// Creates [`StringView`] of a whole [`str`] slice.
58    ///
59    /// see [`StringView::new_part`] to view part of [`str`] slice.
60    pub fn new(base: &'a str) -> Self {
61        Self {
62            base,
63            view_start: 0,
64            view_len: base.len(),
65        }
66    }
67
68    /// Creates [`StringView`] of a part of [`str`] slice using 2 byte indexes.
69    ///
70    /// ```rust
71    /// use string_view::StringView;
72    ///
73    /// let text = "Hello World";
74    /// let view = StringView::new_part(text, 6, 11);
75    ///
76    /// assert_eq!(view.as_str(), "World");
77    /// ```
78    ///
79    /// Or using [`StrExt`] extension trait:
80    ///
81    /// ```rust
82    /// use string_view::StrExt;
83    ///
84    /// let text = "Hello World";
85    /// let view = text.view_part(6, 11);
86    ///
87    /// assert_eq!(view.as_str(), "World");
88    /// ```
89    pub fn new_part(base: &'a str, view_start: usize, view_end: usize) -> Self {
90        assert!(
91            view_end >= view_start,
92            "View end index cannot be less then start index"
93        );
94        Self {
95            base,
96            view_start,
97            view_len: view_end - view_start,
98        }
99    }
100
101    /// Byte index of view start inside [`StringView::base`].
102    pub fn start(&self) -> usize {
103        self.view_start
104    }
105
106    /// Byte index of view end inside [`StringView::base`].
107    pub fn end(&self) -> usize {
108        self.view_start + self.view_len
109    }
110
111    pub fn as_str(&self) -> &str {
112        &self.base[self.view_start..self.view_start + self.view_len]
113    }
114
115    /// Shrinks this view to current left edge with length zero.
116    pub fn shrink_left(&mut self) {
117        self.view_start += self.view_len;
118        self.view_len = 0;
119    }
120
121    /// Shrinks this view to current right edge with length zero.
122    pub fn shrink_right(&mut self) {
123        self.view_len = 0;
124    }
125
126    /// Extend string view to the right by `n` characters.
127    ///
128    /// panics if there is not enough characters in base string to the right of this view.
129    ///
130    /// see [`StringView::try_extend`] for fallible version.
131    ///
132    /// ```rust
133    /// use string_view::StrExt;
134    ///
135    /// let text = "Hello World";
136    /// let mut view = text.view_part(0, 5);
137    /// assert_eq!(view.as_str(), "Hello");
138    ///
139    /// view.extend(6);
140    /// assert_eq!(view.as_str(), "Hello World");
141    /// ```
142    pub fn extend(&mut self, n: usize) {
143        self.try_extend(n)
144            .expect("Unable to extend string view to the right")
145    }
146
147    /// Try to extend string view to the right by `n` characters.
148    ///
149    /// returns [`Err`] if there is not enough characters in base string to the right of this view.
150    ///
151    /// ```rust
152    /// use string_view::StrExt;
153    ///
154    /// let text = "Hello World";
155    ///
156    /// let mut view = text.view_part(0, 5);
157    /// assert_eq!(view.as_str(), "Hello");
158    ///
159    /// let err = view.try_extend(4);
160    /// assert!(err.is_ok());
161    /// assert_eq!(view.as_str(), "Hello Wor");
162    ///
163    /// let err = view.try_extend(10);
164    /// assert!(matches!(err, Err(BaseStringIsTooShort)));
165    /// assert_eq!(view.as_str(), "Hello Wor");
166    /// ```
167    pub fn try_extend(&mut self, n: usize) -> Result<(), BaseStringIsTooShort<RIGHT>> {
168        let mut combined_len = 0;
169        let mut char_iter = self.base[self.end()..].chars();
170        for _ in 0..n {
171            combined_len += char_iter.next().ok_or(BaseStringIsTooShort)?.len_utf8();
172        }
173        self.view_len += combined_len;
174        Ok(())
175    }
176
177    /// Extend string view to the right while `func` returns `true`.
178    ///
179    /// ```rust
180    /// use string_view::StrExt;
181    ///
182    /// let text = "Hello World !!!";
183    ///
184    /// let mut view = text.view_part(0, 2);
185    /// assert_eq!(view.as_str(), "He");
186    ///
187    /// view.extend_while(|ch| ch != ' ');
188    /// assert_eq!(view.as_str(), "Hello");
189    ///
190    /// view.extend(1);
191    /// view.extend_while(|ch| ch != ' ');
192    /// assert_eq!(view.as_str(), "Hello World");
193    /// ```
194    pub fn extend_while<F>(&mut self, mut func: F)
195    where
196        F: FnMut(char) -> bool,
197    {
198        let mut combined_len = 0;
199
200        for ch in self.base[self.end()..].chars() {
201            if func(ch) {
202                combined_len += ch.len_utf8();
203            }
204            else {
205                break;
206            }
207        }
208        self.view_len += combined_len;
209    }
210
211    /// Reduce string view from the right by `n` characters.
212    ///
213    /// panics if there is not enough characters in current string view.
214    ///
215    /// ```rust
216    /// use string_view::StrExt;
217    ///
218    /// let text = "Hello World";
219    ///
220    /// let mut view = text.view();
221    /// assert_eq!(view.as_str(), "Hello World");
222    ///
223    /// view.reduce(6);
224    /// assert_eq!(view.as_str(), "Hello");
225    /// ```
226    pub fn reduce(&mut self, n: usize) {
227        self.try_reduce(n)
228            .expect("Unable to reduce string view from the right")
229    }
230
231    /// Try to reduce string view from the right by `n` characters.
232    ///
233    /// returns [`Err`] if there is not enough characters in current string view.
234    ///
235    /// ```rust
236    /// use string_view::StrExt;
237    ///
238    /// let text = "One and only Hello World";
239    ///
240    /// let mut view = text.view_part(13, 24);
241    /// assert_eq!(view.as_str(), "Hello World");
242    ///
243    /// let result = view.try_reduce(6);
244    /// assert!(result.is_ok());
245    /// assert_eq!(view.as_str(), "Hello");
246    ///
247    /// let result = view.try_reduce(10);
248    /// assert!(matches!(result, Err(ViewIsTooShort)));
249    /// assert_eq!(view.as_str(), "Hello");
250    /// ```
251    pub fn try_reduce(&mut self, n: usize) -> Result<(), ViewIsTooShort<RIGHT>> {
252        let mut combined_len = 0;
253        let mut char_iter = self.base[self.start()..self.end()].chars().rev();
254        for _ in 0..n {
255            combined_len += char_iter.next().ok_or(ViewIsTooShort)?.len_utf8();
256        }
257        self.view_len -= combined_len;
258        Ok(())
259    }
260
261    /// Reduce string view from the right while `func` returns `true`.
262    ///
263    /// ```rust
264    /// use string_view::StrExt;
265    ///
266    /// let text = "Hello World !!!";
267    ///
268    /// let mut view = text.view();
269    /// assert_eq!(view.as_str(), "Hello World !!!");
270    ///
271    /// view.reduce_while(|ch| ch != ' ');
272    /// assert_eq!(view.as_str(), "Hello World ");
273    ///
274    /// view.reduce(1);
275    /// view.reduce_while(|ch| ch != ' ');
276    /// assert_eq!(view.as_str(), "Hello ");
277    /// ```
278    pub fn reduce_while<F>(&mut self, mut func: F)
279    where
280        F: FnMut(char) -> bool,
281    {
282        let mut combined_len = 0;
283        for ch in self.base[self.start()..self.end()].chars().rev() {
284            if func(ch) {
285                combined_len += ch.len_utf8();
286            }
287            else {
288                break;
289            }
290        }
291        self.view_len -= combined_len;
292    }
293
294    /// Extend string view to the left by `n` characters.
295    ///
296    /// panics if there is not enough characters in base string to the left of this view.
297    ///
298    /// see [`StringView::try_extend_left`] for fallible version.
299    ///
300    /// see [`StringView::extend`] to extend to the right.
301    ///
302    /// ```rust
303    /// use string_view::StrExt;
304    ///
305    /// let text = "Hello World";
306    /// let mut view = text.view_part(6, 11);
307    /// assert_eq!(view.as_str(), "World");
308    ///
309    /// view.extend_left(6);
310    /// assert_eq!(view.as_str(), "Hello World");
311    /// ```
312    pub fn extend_left(&mut self, n: usize) {
313        self.try_extend_left(n)
314            .expect("Unable to extend string view to the left")
315    }
316
317    /// Try to extend string view to the left by `n` characters.
318    ///
319    /// returns [`Err`] if there is not enough characters in base string to the right of this view.
320    ///
321    /// see [`StringView::try_extend`] to extend to the right.
322    ///
323    /// ```rust
324    /// use string_view::StrExt;
325    ///
326    /// let text = "Hello World";
327    ///
328    /// let mut view = text.view_part(6, 11);
329    /// assert_eq!(view.as_str(), "World");
330    ///
331    /// let err = view.try_extend_left(4);
332    /// assert!(err.is_ok());
333    /// assert_eq!(view.as_str(), "llo World");
334    ///
335    /// let err = view.try_extend_left(10);
336    /// assert!(matches!(err, Err(BaseStringIsTooShort)));
337    /// assert_eq!(view.as_str(), "llo World");
338    /// ```
339    pub fn try_extend_left(&mut self, n: usize) -> Result<(), BaseStringIsTooShort<LEFT>> {
340        let mut combined_len = 0;
341        let mut char_iter = self.base[..self.start()].chars().rev();
342        for _ in 0..n {
343            combined_len += char_iter.next().ok_or(BaseStringIsTooShort)?.len_utf8();
344        }
345        self.view_start -= combined_len;
346        self.view_len += combined_len;
347        Ok(())
348    }
349
350    /// Extend string view to the left while `func` returns `true`.
351    ///
352    /// see [`StringView::extend_while`] to extend to the right.
353    ///
354    /// ```rust
355    /// use string_view::StrExt;
356    ///
357    /// let text = "Hello World !!!";
358    ///
359    /// let mut view = text.view_part(14, 15);
360    /// assert_eq!(view.as_str(), "!");
361    ///
362    /// view.extend_left_while(|ch| ch != ' ');
363    /// assert_eq!(view.as_str(), "!!!");
364    ///
365    /// view.extend_left(1);
366    /// view.extend_left_while(|ch| ch != ' ');
367    /// assert_eq!(view.as_str(), "World !!!");
368    /// ```
369    pub fn extend_left_while<F>(&mut self, mut func: F)
370    where
371        F: FnMut(char) -> bool,
372    {
373        let mut combined_len = 0;
374
375        for ch in self.base[..self.start()].chars().rev() {
376            if func(ch) {
377                combined_len += ch.len_utf8();
378            }
379            else {
380                break;
381            }
382        }
383        self.view_start -= combined_len;
384        self.view_len += combined_len;
385    }
386
387    /// Reduce string view from the left by `n` characters.
388    ///
389    /// panics if there is not enough characters in current string view.
390    ///
391    /// see [`StringView::try_reduce_left`] for fallible version.
392    ///
393    /// see [`StringView::reduce`] to reduce from the right.
394    ///
395    /// ```rust
396    /// use string_view::StrExt;
397    ///
398    /// let text = "Hello World";
399    ///
400    /// let mut view = text.view();
401    /// assert_eq!(view.as_str(), "Hello World");
402    ///
403    /// view.reduce_left(6);
404    /// assert_eq!(view.as_str(), "World");
405    /// ```
406    pub fn reduce_left(&mut self, n: usize) {
407        self.try_reduce_left(n)
408            .expect("Unable to reduce string view from the left")
409    }
410
411    /// Try to reduce string view from the left by `n` characters.
412    ///
413    /// returns [`Err`] if there is not enough characters in current string view.
414    ///
415    /// see [`StringView::try_reduce`] to reduce from the right.
416    ///
417    /// ```rust
418    /// use string_view::StrExt;
419    ///
420    /// let text = "One and only Hello World";
421    ///
422    /// let mut view = text.view_part(13, 24);
423    /// assert_eq!(view.as_str(), "Hello World");
424    ///
425    /// let result = view.try_reduce_left(6);
426    /// assert!(result.is_ok());
427    /// assert_eq!(view.as_str(), "World");
428    ///
429    /// let result = view.try_reduce_left(10);
430    /// assert!(matches!(result, Err(ViewIsTooShort)));
431    /// assert_eq!(view.as_str(), "World");
432    /// ```
433    pub fn try_reduce_left(&mut self, n: usize) -> Result<(), ViewIsTooShort<LEFT>> {
434        let mut combined_len = 0;
435        let mut char_iter = self.base[self.start()..self.end()].chars();
436        for _ in 0..n {
437            combined_len += char_iter.next().ok_or(ViewIsTooShort)?.len_utf8();
438        }
439        self.view_start += combined_len;
440        self.view_len -= combined_len;
441        Ok(())
442    }
443
444    /// Reduce string view from the left while `func` returns `true`.
445    ///
446    /// see [`StringView::reduce_while`] to reduce from the right.
447    ///
448    /// ```rust
449    /// use string_view::StrExt;
450    ///
451    /// let text = "Hello World !!!";
452    ///
453    /// let mut view = text.view();
454    /// assert_eq!(view.as_str(), "Hello World !!!");
455    ///
456    /// view.reduce_left_while(|ch| ch != ' ');
457    /// assert_eq!(view.as_str(), " World !!!");
458    ///
459    /// view.reduce_left(1);
460    /// view.reduce_left_while(|ch| ch != ' ');
461    /// assert_eq!(view.as_str(), " !!!");
462    /// ```
463    pub fn reduce_left_while<F>(&mut self, mut func: F)
464    where
465        F: FnMut(char) -> bool,
466    {
467        let mut combined_len = 0;
468        for ch in self.base[self.start()..self.end()].chars() {
469            if func(ch) {
470                combined_len += ch.len_utf8();
471            }
472            else {
473                break;
474            }
475        }
476        self.view_start += combined_len;
477        self.view_len -= combined_len;
478    }
479}
480
481impl Deref for StringView<'_> {
482    type Target = str;
483
484    fn deref(&self) -> &Self::Target {
485        self.as_str()
486    }
487}
488
489impl Debug for StringView<'_> {
490    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
491        Debug::fmt(self.as_str(), f)
492    }
493}
494
495impl Display for StringView<'_> {
496    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
497        Display::fmt(self.as_str(), f)
498    }
499}
500
501type Side = bool;
502const RIGHT: bool = true;
503const LEFT: bool = false;
504
505/// The only error case in [`StringView::try_extend`].
506pub struct BaseStringIsTooShort<const SIDE: Side>;
507
508impl<const SIDE: Side> Debug for BaseStringIsTooShort<SIDE> {
509    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
510        write!(
511            f,
512            "Base String contains less characters than `n` to the {} of the view",
513            if SIDE == RIGHT { "right" } else { "left" }
514        )
515    }
516}
517
518impl<const SIDE: Side> Display for BaseStringIsTooShort<SIDE> {
519    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
520        Debug::fmt(&self, f)
521    }
522}
523
524impl<const SIDE: Side> Error for BaseStringIsTooShort<SIDE> {}
525
526/// The only error case in [`StringView::try_reduce`].
527pub struct ViewIsTooShort<const SIDE: Side>;
528
529impl<const SIDE: Side> Debug for ViewIsTooShort<SIDE> {
530    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
531        write!(
532            f,
533            "View contains less characters than `n` to the {} of the view",
534            if SIDE == RIGHT { "right" } else { "left" }
535        )
536    }
537}
538
539impl<const SIDE: Side> Display for ViewIsTooShort<SIDE> {
540    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
541        Debug::fmt(&self, f)
542    }
543}
544
545impl<const SIDE: Side> Error for ViewIsTooShort<SIDE> {}
546
547/// In-place character representation inside str slice
548///
549/// Under the hood is &str with single character
550///
551/// ```rust
552/// use string_view::Char;
553///
554/// let ch = Char::new("A");
555/// ```
556///
557/// ```rust,should_panic
558/// use string_view::Char;
559///
560/// let ch = Char::new(""); // panics
561/// let ch = Char::new("Hello"); // panics
562/// ```
563pub struct Char<'a>(&'a str);
564
565impl Char<'_> {
566    pub fn new(ch: &str) -> Char<'_> {
567        let char_len = ch
568            .chars()
569            .next()
570            .expect("Unable to create Char from empty string")
571            .len_utf8();
572
573        assert_eq!(
574            char_len,
575            ch.len(),
576            "Char can only be constructed from single character string"
577        );
578
579        Char(ch)
580    }
581
582    pub fn char(&self) -> char {
583        self.0.chars().next().unwrap()
584    }
585
586    pub fn as_str(&self) -> &str {
587        self.0
588    }
589}
590
591impl<'a> Deref for Char<'a> {
592    type Target = str;
593
594    fn deref(&self) -> &str {
595        self.0
596    }
597}
598
599impl Debug for Char<'_> {
600    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
601        Debug::fmt(&self.0, f)
602    }
603}
604
605impl Display for Char<'_> {
606    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
607        Display::fmt(&self.0, f)
608    }
609}
610
611/// Iterator of chars in-place
612pub struct CharsInPlace<'a>(&'a str);
613
614impl<'a> Iterator for CharsInPlace<'a> {
615    type Item = Char<'a>;
616
617    fn next(&mut self) -> Option<Self::Item> {
618        let next_char_len = self.0.chars().next()?.len_utf8();
619
620        let (this, rest) = self.0.split_at(next_char_len);
621        self.0 = rest;
622
623        Some(Char(this))
624    }
625}
626
627/// In-place character representation inside mutable str slice
628///
629/// Convert to [`Char`] using [`CharMut::as_char`].
630pub struct CharMut<'a>(&'a mut str);
631
632impl CharMut<'_> {
633    pub fn new(ch: &mut str) -> CharMut<'_> {
634        let char_len = ch
635            .chars()
636            .next()
637            .expect("Unable to create CharMut from empty string")
638            .len_utf8();
639
640        assert_eq!(
641            char_len,
642            ch.len(),
643            "CharMut can only be constructed from single character string"
644        );
645
646        CharMut(ch)
647    }
648
649    pub fn char(&self) -> char {
650        self.0.chars().next().unwrap()
651    }
652
653    pub fn as_str(&self) -> &str {
654        self.0
655    }
656
657    pub fn as_str_mut(&mut self) -> &mut str {
658        self.0
659    }
660
661    pub fn as_char(&self) -> Char<'_> {
662        Char(&self.0)
663    }
664}
665
666impl<'a> Deref for CharMut<'a> {
667    type Target = str;
668
669    fn deref(&self) -> &str {
670        self.0
671    }
672}
673
674impl<'a> DerefMut for CharMut<'a> {
675    fn deref_mut(&mut self) -> &mut Self::Target {
676        self.0
677    }
678}
679
680impl Debug for CharMut<'_> {
681    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
682        Debug::fmt(&self.0, f)
683    }
684}
685
686impl Display for CharMut<'_> {
687    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
688        Display::fmt(&self.0, f)
689    }
690}
691
692/// Mutable iterator of chars in-place
693pub struct CharsInPlaceMut<'a>(&'a mut str);
694
695impl<'a> Iterator for CharsInPlaceMut<'a> {
696    type Item = CharMut<'a>;
697
698    fn next(&mut self) -> Option<Self::Item> {
699        let next_char_len = self.0.chars().next()?.len_utf8();
700
701        let this: &mut str = core::mem::take(&mut self.0);
702        let (this, rest) = this.split_at_mut(next_char_len);
703        self.0 = rest;
704
705        Some(CharMut(this))
706    }
707}
708
709pub trait StrExt {
710    fn view(&self) -> StringView<'_>;
711    fn view_part(&self, start_idx: usize, end_idx: usize) -> StringView<'_>;
712
713    /// Byte index of this [`Char`] start & end inside base [`str`].
714    ///
715    /// ```rust
716    /// use string_view::StrExt;
717    ///
718    /// let text = "Hello World";
719    /// let mut chars = text.chars_in_place();
720    ///
721    /// let first_char = chars.next().unwrap();
722    /// assert_eq!(first_char.as_str(), "H");
723    /// assert_eq!(text.char_idx(first_char), (0, 1));
724    ///
725    /// let second_char = chars.next().unwrap();
726    /// assert_eq!(second_char.as_str(), "e");
727    /// assert_eq!(text.char_idx(second_char), (1, 2));
728    /// ```
729    fn char_idx(&self, ch: Char) -> (usize, usize);
730
731    fn chars_in_place(&self) -> CharsInPlace<'_>;
732    fn chars_in_place_mut(&mut self) -> CharsInPlaceMut<'_>;
733}
734
735impl StrExt for str {
736    fn view(&self) -> StringView<'_> {
737        StringView::new(self)
738    }
739    fn view_part(&self, start_idx: usize, end_idx: usize) -> StringView<'_> {
740        StringView::new_part(self, start_idx, end_idx)
741    }
742    fn chars_in_place(&self) -> CharsInPlace<'_> {
743        CharsInPlace(self)
744    }
745    fn chars_in_place_mut(&mut self) -> CharsInPlaceMut<'_> {
746        CharsInPlaceMut(self)
747    }
748
749    /// Byte index of this [`Char`] start & end inside base [`str`].
750    ///
751    /// ```rust
752    /// use string_view::StrExt;
753    ///
754    /// let text = "Hello World";
755    /// let mut chars = text.chars_in_place();
756    ///
757    /// let first_char = chars.next().unwrap();
758    /// assert_eq!(first_char.as_str(), "H");
759    /// assert_eq!(text.char_idx(first_char), (0, 1));
760    ///
761    /// let second_char = chars.next().unwrap();
762    /// assert_eq!(second_char.as_str(), "e");
763    /// assert_eq!(text.char_idx(second_char), (1, 2));
764    /// ```
765    fn char_idx(&self, ch: Char) -> (usize, usize) {
766        let str_start = self.as_ptr() as usize;
767        let str_end = str_start + self.len();
768        let ch_start = ch.as_ptr() as usize;
769        let ch_end = ch_start + ch.len();
770
771        let range = str_start..str_end;
772        assert!(
773            range.contains(&ch_start),
774            "Char has to be inside this string to get its index"
775        );
776        assert!(
777            range.contains(&ch_end),
778            "Char has to be inside this string to get its index"
779        );
780
781        (ch_start - str_start, ch_end - str_start)
782    }
783}
784
785#[cfg(test)]
786mod test;