@@ -216,10 +216,13 @@ impl AsciiStr {
216
216
/// ```
217
217
#[ must_use]
218
218
pub fn trim_start ( & self ) -> & Self {
219
- & self [ self
219
+ let whitespace_len = self
220
220
. chars ( )
221
- . take_while ( |ascii| ascii. is_whitespace ( ) )
222
- . count ( ) ..]
221
+ . position ( |ascii| !ascii. is_whitespace ( ) )
222
+ . unwrap_or_else ( || self . len ( ) ) ;
223
+
224
+ // SAFETY: `whitespace_len` is `0..=len`, which is at most `len`, which is a valid empty slice.
225
+ unsafe { self . as_slice ( ) . get_unchecked ( whitespace_len..) . into ( ) }
223
226
}
224
227
225
228
/// Returns an ASCII string slice with trailing whitespace removed.
@@ -232,12 +235,19 @@ impl AsciiStr {
232
235
/// ```
233
236
#[ must_use]
234
237
pub fn trim_end ( & self ) -> & Self {
235
- let trimmed = self
238
+ // Number of whitespace characters counting from the end
239
+ let whitespace_len = self
236
240
. chars ( )
237
241
. rev ( )
238
- . take_while ( |ch| ch. is_whitespace ( ) )
239
- . count ( ) ;
240
- & self [ ..self . len ( ) - trimmed]
242
+ . position ( |ascii| !ascii. is_whitespace ( ) )
243
+ . unwrap_or_else ( || self . len ( ) ) ;
244
+
245
+ // SAFETY: `whitespace_len` is `0..=len`, which is at most `len`, which is a valid empty slice, and at least `0`, which is the whole slice.
246
+ unsafe {
247
+ self . as_slice ( )
248
+ . get_unchecked ( ..self . len ( ) - whitespace_len)
249
+ . into ( )
250
+ }
241
251
}
242
252
243
253
/// Compares two strings case-insensitively.
@@ -470,6 +480,7 @@ impl fmt::Debug for AsciiStr {
470
480
471
481
macro_rules! impl_index {
472
482
( $idx: ty) => {
483
+ #[ allow( clippy:: indexing_slicing) ] // In `Index`, if it's out of bounds, panic is the default
473
484
impl Index <$idx> for AsciiStr {
474
485
type Output = AsciiStr ;
475
486
@@ -479,6 +490,7 @@ macro_rules! impl_index {
479
490
}
480
491
}
481
492
493
+ #[ allow( clippy:: indexing_slicing) ] // In `IndexMut`, if it's out of bounds, panic is the default
482
494
impl IndexMut <$idx> for AsciiStr {
483
495
#[ inline]
484
496
fn index_mut( & mut self , index: $idx) -> & mut AsciiStr {
@@ -495,6 +507,7 @@ impl_index! { RangeFull }
495
507
impl_index ! { RangeInclusive <usize > }
496
508
impl_index ! { RangeToInclusive <usize > }
497
509
510
+ #[ allow( clippy:: indexing_slicing) ] // In `Index`, if it's out of bounds, panic is the default
498
511
impl Index < usize > for AsciiStr {
499
512
type Output = AsciiChar ;
500
513
@@ -504,6 +517,7 @@ impl Index<usize> for AsciiStr {
504
517
}
505
518
}
506
519
520
+ #[ allow( clippy:: indexing_slicing) ] // In `IndexMut`, if it's out of bounds, panic is the default
507
521
impl IndexMut < usize > for AsciiStr {
508
522
#[ inline]
509
523
fn index_mut ( & mut self , index : usize ) -> & mut AsciiChar {
@@ -636,13 +650,14 @@ struct Split<'a> {
636
650
impl < ' a > Iterator for Split < ' a > {
637
651
type Item = & ' a AsciiStr ;
638
652
639
- #[ allow( clippy:: option_if_let_else) ] // There are side effects with `else` so we don't use iterator adaptors
640
653
fn next ( & mut self ) -> Option < & ' a AsciiStr > {
641
654
if !self . ended {
642
655
let start: & AsciiStr = self . chars . as_str ( ) ;
643
656
let split_on = self . on ;
644
- if let Some ( at) = self . chars . position ( |ch| ch == split_on) {
645
- Some ( & start[ ..at] )
657
+
658
+ if let Some ( at) = self . chars . position ( |ascii| ascii == split_on) {
659
+ // SAFETY: `at` is guaranteed to be in bounds, as `position` returns `Ok(0..len)`.
660
+ Some ( unsafe { start. as_slice ( ) . get_unchecked ( ..at) . into ( ) } )
646
661
} else {
647
662
self . ended = true ;
648
663
Some ( start)
@@ -653,13 +668,14 @@ impl<'a> Iterator for Split<'a> {
653
668
}
654
669
}
655
670
impl < ' a > DoubleEndedIterator for Split < ' a > {
656
- #[ allow( clippy:: option_if_let_else) ] // There are side effects with `else` so we don't use iterator adaptors
657
671
fn next_back ( & mut self ) -> Option < & ' a AsciiStr > {
658
672
if !self . ended {
659
673
let start: & AsciiStr = self . chars . as_str ( ) ;
660
674
let split_on = self . on ;
661
- if let Some ( at) = self . chars . rposition ( |ch| ch == split_on) {
662
- Some ( & start[ at + 1 ..] )
675
+
676
+ if let Some ( at) = self . chars . rposition ( |ascii| ascii == split_on) {
677
+ // SAFETY: `at` is guaranteed to be in bounds, as `rposition` returns `Ok(0..len)`, and slices `1..`, `2..`, etc... until `len..` inclusive, are valid.
678
+ Some ( unsafe { start. as_slice ( ) . get_unchecked ( at + 1 ..) . into ( ) } )
663
679
} else {
664
680
self . ended = true ;
665
681
Some ( start)
@@ -684,40 +700,65 @@ impl<'a> Iterator for Lines<'a> {
684
700
. chars ( )
685
701
. position ( |chr| chr == AsciiChar :: LineFeed )
686
702
{
687
- let line = if idx > 0 && self . string [ idx - 1 ] == AsciiChar :: CarriageReturn {
688
- & self . string [ ..idx - 1 ]
703
+ // SAFETY: `idx` is guaranteed to be `1..len`, as we get it from `position` as `0..len` and make sure it's not `0`.
704
+ let line = if idx > 0
705
+ && * unsafe { self . string . as_slice ( ) . get_unchecked ( idx - 1 ) }
706
+ == AsciiChar :: CarriageReturn
707
+ {
708
+ // SAFETY: As per above, `idx` is guaranteed to be `1..len`
709
+ unsafe { self . string . as_slice ( ) . get_unchecked ( ..idx - 1 ) . into ( ) }
689
710
} else {
690
- & self . string [ ..idx]
711
+ // SAFETY: As per above, `idx` is guaranteed to be `0..len`
712
+ unsafe { self . string . as_slice ( ) . get_unchecked ( ..idx) . into ( ) }
691
713
} ;
692
- self . string = & self . string [ idx + 1 ..] ;
714
+ // SAFETY: As per above, `idx` is guaranteed to be `0..len`, so at the extreme, slicing `len..` is a valid empty slice.
715
+ self . string = unsafe { self . string . as_slice ( ) . get_unchecked ( idx + 1 ..) . into ( ) } ;
693
716
Some ( line)
694
717
} else if self . string . is_empty ( ) {
695
718
None
696
719
} else {
697
720
let line = self . string ;
698
- self . string = & self . string [ ..0 ] ;
721
+ // SAFETY: Slicing `..0` is always valid and yields an empty slice
722
+ self . string = unsafe { self . string . as_slice ( ) . get_unchecked ( ..0 ) . into ( ) } ;
699
723
Some ( line)
700
724
}
701
725
}
702
726
}
727
+
703
728
impl < ' a > DoubleEndedIterator for Lines < ' a > {
704
729
fn next_back ( & mut self ) -> Option < & ' a AsciiStr > {
705
730
if self . string . is_empty ( ) {
706
731
return None ;
707
732
}
708
- let mut i = self . string . len ( ) ;
709
- if self . string [ i - 1 ] == AsciiChar :: LineFeed {
710
- i -= 1 ;
711
- if i > 0 && self . string [ i - 1 ] == AsciiChar :: CarriageReturn {
712
- i -= 1 ;
713
- }
714
- }
715
- self . string = & self . string [ ..i] ;
716
- while i > 0 && self . string [ i - 1 ] != AsciiChar :: LineFeed {
717
- i -= 1 ;
733
+
734
+ // If we end with `LF` / `CR/LF`, remove them
735
+ if let [ slice @ .., AsciiChar :: CarriageReturn , AsciiChar :: LineFeed ]
736
+ | [ slice @ .., AsciiChar :: LineFeed ] = self . string . as_slice ( )
737
+ {
738
+ self . string = slice. into ( ) ;
718
739
}
719
- let line = & self . string [ i..] ;
720
- self . string = & self . string [ ..i] ;
740
+
741
+ // SAFETY: This will never be `0`, as we remove any `LF` from the end, it is `1..len`
742
+ let lf_rev_pos = self
743
+ . string
744
+ . chars ( )
745
+ . rev ( )
746
+ . position ( |ascii| ascii == AsciiChar :: LineFeed )
747
+ . unwrap_or_else ( || self . string . len ( ) ) ;
748
+
749
+ // SAFETY: As per above, `self.len() - lf_rev_pos` will be in range `0..len - 1`, so both indexes are correct.
750
+ let line = unsafe {
751
+ self . string
752
+ . as_slice ( )
753
+ . get_unchecked ( self . string . len ( ) - lf_rev_pos..)
754
+ . into ( )
755
+ } ;
756
+ self . string = unsafe {
757
+ self . string
758
+ . as_slice ( )
759
+ . get_unchecked ( ..self . string . len ( ) - lf_rev_pos)
760
+ . into ( )
761
+ } ;
721
762
Some ( line)
722
763
}
723
764
}
@@ -975,7 +1016,6 @@ impl AsMutAsciiStr for [AsciiChar] {
975
1016
impl AsAsciiStr for [ u8 ] {
976
1017
type Inner = u8 ;
977
1018
978
- #[ allow( clippy:: option_if_let_else) ] // `if` needs to use past results, so it's more expressive like this
979
1019
fn slice_ascii < R > ( & self , range : R ) -> Result < & AsciiStr , AsAsciiStrError >
980
1020
where
981
1021
R : SliceIndex < [ u8 ] , Output = [ u8 ] > ,
@@ -1006,7 +1046,6 @@ impl AsAsciiStr for [u8] {
1006
1046
}
1007
1047
}
1008
1048
impl AsMutAsciiStr for [ u8 ] {
1009
- #[ allow( clippy:: option_if_let_else) ] // `if` needs to use past results, so it's more expressive like this
1010
1049
fn slice_ascii_mut < R > ( & mut self , range : R ) -> Result < & mut AsciiStr , AsAsciiStrError >
1011
1050
where
1012
1051
R : SliceIndex < [ u8 ] , Output = [ u8 ] > ,
0 commit comments