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;