Skip to content

Commit 9ee373f

Browse files
authored
Rollup merge of rust-lang#69784 - benesch:fast-strip-prefix-suffix, r=kennytm
Optimize strip_prefix and strip_suffix with str patterns As mentioned in rust-lang#67302 (comment). I'm not sure whether adding these methods to `Pattern` is desirable—but they have default implementations so the change is backwards compatible. Plus it seems like they're slated for wholesale replacement soon anyway? rust-lang#56345 ---- Constructing a Searcher in strip_prefix and strip_suffix is unnecessarily slow when the pattern is a fixed-length string. Add strip_prefix and strip_suffix methods to the Pattern trait, and add optimized implementations of these methods in the str implementation. The old implementation is retained as the default for these methods.
2 parents 2113659 + ac478f2 commit 9ee373f

File tree

3 files changed

+107
-30
lines changed

3 files changed

+107
-30
lines changed

src/liballoc/string.rs

+15
Original file line numberDiff line numberDiff line change
@@ -1849,6 +1849,21 @@ impl<'a, 'b> Pattern<'a> for &'b String {
18491849
fn is_prefix_of(self, haystack: &'a str) -> bool {
18501850
self[..].is_prefix_of(haystack)
18511851
}
1852+
1853+
#[inline]
1854+
fn strip_prefix_of(self, haystack: &'a str) -> Option<&'a str> {
1855+
self[..].strip_prefix_of(haystack)
1856+
}
1857+
1858+
#[inline]
1859+
fn is_suffix_of(self, haystack: &'a str) -> bool {
1860+
self[..].is_suffix_of(haystack)
1861+
}
1862+
1863+
#[inline]
1864+
fn strip_suffix_of(self, haystack: &'a str) -> Option<&'a str> {
1865+
self[..].strip_suffix_of(haystack)
1866+
}
18521867
}
18531868

18541869
#[stable(feature = "rust1", since = "1.0.0")]

src/libcore/str/mod.rs

+7-30
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#![stable(feature = "rust1", since = "1.0.0")]
1010

1111
use self::pattern::Pattern;
12-
use self::pattern::{DoubleEndedSearcher, ReverseSearcher, SearchStep, Searcher};
12+
use self::pattern::{DoubleEndedSearcher, ReverseSearcher, Searcher};
1313

1414
use crate::char;
1515
use crate::fmt::{self, Write};
@@ -3986,26 +3986,15 @@ impl str {
39863986
/// ```
39873987
/// #![feature(str_strip)]
39883988
///
3989-
/// assert_eq!("foobar".strip_prefix("foo"), Some("bar"));
3990-
/// assert_eq!("foobar".strip_prefix("bar"), None);
3989+
/// assert_eq!("foo:bar".strip_prefix("foo:"), Some("bar"));
3990+
/// assert_eq!("foo:bar".strip_prefix("bar"), None);
39913991
/// assert_eq!("foofoo".strip_prefix("foo"), Some("foo"));
39923992
/// ```
39933993
#[must_use = "this returns the remaining substring as a new slice, \
39943994
without modifying the original"]
39953995
#[unstable(feature = "str_strip", reason = "newly added", issue = "67302")]
39963996
pub fn strip_prefix<'a, P: Pattern<'a>>(&'a self, prefix: P) -> Option<&'a str> {
3997-
let mut matcher = prefix.into_searcher(self);
3998-
if let SearchStep::Match(start, len) = matcher.next() {
3999-
debug_assert_eq!(
4000-
start, 0,
4001-
"The first search step from Searcher \
4002-
must include the first character"
4003-
);
4004-
// SAFETY: `Searcher` is known to return valid indices.
4005-
unsafe { Some(self.get_unchecked(len..)) }
4006-
} else {
4007-
None
4008-
}
3997+
prefix.strip_prefix_of(self)
40093998
}
40103999

40114000
/// Returns a string slice with the suffix removed.
@@ -4020,8 +4009,8 @@ impl str {
40204009
///
40214010
/// ```
40224011
/// #![feature(str_strip)]
4023-
/// assert_eq!("barfoo".strip_suffix("foo"), Some("bar"));
4024-
/// assert_eq!("barfoo".strip_suffix("bar"), None);
4012+
/// assert_eq!("bar:foo".strip_suffix(":foo"), Some("bar"));
4013+
/// assert_eq!("bar:foo".strip_suffix("bar"), None);
40254014
/// assert_eq!("foofoo".strip_suffix("foo"), Some("foo"));
40264015
/// ```
40274016
#[must_use = "this returns the remaining substring as a new slice, \
@@ -4032,19 +4021,7 @@ impl str {
40324021
P: Pattern<'a>,
40334022
<P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,
40344023
{
4035-
let mut matcher = suffix.into_searcher(self);
4036-
if let SearchStep::Match(start, end) = matcher.next_back() {
4037-
debug_assert_eq!(
4038-
end,
4039-
self.len(),
4040-
"The first search step from ReverseSearcher \
4041-
must include the last character"
4042-
);
4043-
// SAFETY: `Searcher` is known to return valid indices.
4044-
unsafe { Some(self.get_unchecked(..start)) }
4045-
} else {
4046-
None
4047-
}
4024+
suffix.strip_suffix_of(self)
40484025
}
40494026

40504027
/// Returns a string slice with all suffixes that match a pattern

src/libcore/str/pattern.rs

+85
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,22 @@ pub trait Pattern<'a>: Sized {
4747
matches!(self.into_searcher(haystack).next(), SearchStep::Match(0, _))
4848
}
4949

50+
/// Removes the pattern from the front of haystack, if it matches.
51+
#[inline]
52+
fn strip_prefix_of(self, haystack: &'a str) -> Option<&'a str> {
53+
if let SearchStep::Match(start, len) = self.into_searcher(haystack).next() {
54+
debug_assert_eq!(
55+
start, 0,
56+
"The first search step from Searcher \
57+
must include the first character"
58+
);
59+
// SAFETY: `Searcher` is known to return valid indices.
60+
unsafe { Some(haystack.get_unchecked(len..)) }
61+
} else {
62+
None
63+
}
64+
}
65+
5066
/// Checks whether the pattern matches at the back of the haystack
5167
#[inline]
5268
fn is_suffix_of(self, haystack: &'a str) -> bool
@@ -55,6 +71,26 @@ pub trait Pattern<'a>: Sized {
5571
{
5672
matches!(self.into_searcher(haystack).next_back(), SearchStep::Match(_, j) if haystack.len() == j)
5773
}
74+
75+
/// Removes the pattern from the back of haystack, if it matches.
76+
#[inline]
77+
fn strip_suffix_of(self, haystack: &'a str) -> Option<&'a str>
78+
where
79+
Self::Searcher: ReverseSearcher<'a>,
80+
{
81+
if let SearchStep::Match(start, end) = self.into_searcher(haystack).next_back() {
82+
debug_assert_eq!(
83+
end,
84+
haystack.len(),
85+
"The first search step from ReverseSearcher \
86+
must include the last character"
87+
);
88+
// SAFETY: `Searcher` is known to return valid indices.
89+
unsafe { Some(haystack.get_unchecked(..start)) }
90+
} else {
91+
None
92+
}
93+
}
5894
}
5995

6096
// Searcher
@@ -448,13 +484,26 @@ impl<'a> Pattern<'a> for char {
448484
self.encode_utf8(&mut [0u8; 4]).is_prefix_of(haystack)
449485
}
450486

487+
#[inline]
488+
fn strip_prefix_of(self, haystack: &'a str) -> Option<&'a str> {
489+
self.encode_utf8(&mut [0u8; 4]).strip_prefix_of(haystack)
490+
}
491+
451492
#[inline]
452493
fn is_suffix_of(self, haystack: &'a str) -> bool
453494
where
454495
Self::Searcher: ReverseSearcher<'a>,
455496
{
456497
self.encode_utf8(&mut [0u8; 4]).is_suffix_of(haystack)
457498
}
499+
500+
#[inline]
501+
fn strip_suffix_of(self, haystack: &'a str) -> Option<&'a str>
502+
where
503+
Self::Searcher: ReverseSearcher<'a>,
504+
{
505+
self.encode_utf8(&mut [0u8; 4]).strip_suffix_of(haystack)
506+
}
458507
}
459508

460509
/////////////////////////////////////////////////////////////////////////////
@@ -569,13 +618,26 @@ macro_rules! pattern_methods {
569618
($pmap)(self).is_prefix_of(haystack)
570619
}
571620

621+
#[inline]
622+
fn strip_prefix_of(self, haystack: &'a str) -> Option<&'a str> {
623+
($pmap)(self).strip_prefix_of(haystack)
624+
}
625+
572626
#[inline]
573627
fn is_suffix_of(self, haystack: &'a str) -> bool
574628
where
575629
$t: ReverseSearcher<'a>,
576630
{
577631
($pmap)(self).is_suffix_of(haystack)
578632
}
633+
634+
#[inline]
635+
fn strip_suffix_of(self, haystack: &'a str) -> Option<&'a str>
636+
where
637+
$t: ReverseSearcher<'a>,
638+
{
639+
($pmap)(self).strip_suffix_of(haystack)
640+
}
579641
};
580642
}
581643

@@ -715,11 +777,34 @@ impl<'a, 'b> Pattern<'a> for &'b str {
715777
haystack.as_bytes().starts_with(self.as_bytes())
716778
}
717779

780+
/// Removes the pattern from the front of haystack, if it matches.
781+
#[inline]
782+
fn strip_prefix_of(self, haystack: &'a str) -> Option<&'a str> {
783+
if self.is_prefix_of(haystack) {
784+
// SAFETY: prefix was just verified to exist.
785+
unsafe { Some(haystack.get_unchecked(self.as_bytes().len()..)) }
786+
} else {
787+
None
788+
}
789+
}
790+
718791
/// Checks whether the pattern matches at the back of the haystack
719792
#[inline]
720793
fn is_suffix_of(self, haystack: &'a str) -> bool {
721794
haystack.as_bytes().ends_with(self.as_bytes())
722795
}
796+
797+
/// Removes the pattern from the back of haystack, if it matches.
798+
#[inline]
799+
fn strip_suffix_of(self, haystack: &'a str) -> Option<&'a str> {
800+
if self.is_suffix_of(haystack) {
801+
let i = haystack.len() - self.as_bytes().len();
802+
// SAFETY: suffix was just verified to exist.
803+
unsafe { Some(haystack.get_unchecked(..i)) }
804+
} else {
805+
None
806+
}
807+
}
723808
}
724809

725810
/////////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)