Rollup merge of #142331 - deven:trim_prefix_suffix, r=Amanieu

Add `trim_prefix` and `trim_suffix` methods for both `slice` and `str` types.

Implements `trim_prefix` and `trim_suffix` methods for both `slice` and `str` types, which remove at most one occurrence of a prefix/suffix while always returning a string/slice (rather than Option), enabling easy method chaining.

## Tracking issue
rust-lang/rust#142312

## API
```rust
impl str {
    pub fn trim_prefix<P: Pattern>(&self, prefix: P) -> &str;
    pub fn trim_suffix<P: Pattern>(&self, suffix: P) -> &str
    where
        for<'a> P::Searcher<'a>: ReverseSearcher<'a>;
}

impl<T> [T] {
    pub fn trim_prefix<P: SlicePattern<Item = T> + ?Sized>(&self, prefix: &P) -> &[T]
    where
        T: PartialEq;
    pub fn trim_suffix<P: SlicePattern<Item = T> + ?Sized>(&self, suffix: &P) -> &[T]
    where
        T: PartialEq;
}
```

## Examples
```rust
// Method chaining
assert_eq!(" <https://example.com/> ".trim().trim_prefix('<').trim_suffix('>').trim(), "https://example.com/");

// Slices
let v = &[10, 40, 30];
assert_eq!(v.trim_prefix(&[10]), &[40, 30][..]);
```

## ACP
Originally proposed in rust-lang/libs-team#597
This commit is contained in:
Trevor Gross 2025-06-20 13:35:58 -04:00 committed by GitHub
commit 851fbcb092
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 160 additions and 0 deletions

View file

@ -2764,6 +2764,89 @@ impl<T> [T] {
None
}
/// Returns a subslice with the optional prefix removed.
///
/// If the slice starts with `prefix`, returns the subslice after the prefix. If `prefix`
/// is empty or the slice does not start with `prefix`, simply returns the original slice.
/// If `prefix` is equal to the original slice, returns an empty slice.
///
/// # Examples
///
/// ```
/// #![feature(trim_prefix_suffix)]
///
/// let v = &[10, 40, 30];
///
/// // Prefix present - removes it
/// assert_eq!(v.trim_prefix(&[10]), &[40, 30][..]);
/// assert_eq!(v.trim_prefix(&[10, 40]), &[30][..]);
/// assert_eq!(v.trim_prefix(&[10, 40, 30]), &[][..]);
///
/// // Prefix absent - returns original slice
/// assert_eq!(v.trim_prefix(&[50]), &[10, 40, 30][..]);
/// assert_eq!(v.trim_prefix(&[10, 50]), &[10, 40, 30][..]);
///
/// let prefix : &str = "he";
/// assert_eq!(b"hello".trim_prefix(prefix.as_bytes()), b"llo".as_ref());
/// ```
#[must_use = "returns the subslice without modifying the original"]
#[unstable(feature = "trim_prefix_suffix", issue = "142312")]
pub fn trim_prefix<P: SlicePattern<Item = T> + ?Sized>(&self, prefix: &P) -> &[T]
where
T: PartialEq,
{
// This function will need rewriting if and when SlicePattern becomes more sophisticated.
let prefix = prefix.as_slice();
let n = prefix.len();
if n <= self.len() {
let (head, tail) = self.split_at(n);
if head == prefix {
return tail;
}
}
self
}
/// Returns a subslice with the optional suffix removed.
///
/// If the slice ends with `suffix`, returns the subslice before the suffix. If `suffix`
/// is empty or the slice does not end with `suffix`, simply returns the original slice.
/// If `suffix` is equal to the original slice, returns an empty slice.
///
/// # Examples
///
/// ```
/// #![feature(trim_prefix_suffix)]
///
/// let v = &[10, 40, 30];
///
/// // Suffix present - removes it
/// assert_eq!(v.trim_suffix(&[30]), &[10, 40][..]);
/// assert_eq!(v.trim_suffix(&[40, 30]), &[10][..]);
/// assert_eq!(v.trim_suffix(&[10, 40, 30]), &[][..]);
///
/// // Suffix absent - returns original slice
/// assert_eq!(v.trim_suffix(&[50]), &[10, 40, 30][..]);
/// assert_eq!(v.trim_suffix(&[50, 30]), &[10, 40, 30][..]);
/// ```
#[must_use = "returns the subslice without modifying the original"]
#[unstable(feature = "trim_prefix_suffix", issue = "142312")]
pub fn trim_suffix<P: SlicePattern<Item = T> + ?Sized>(&self, suffix: &P) -> &[T]
where
T: PartialEq,
{
// This function will need rewriting if and when SlicePattern becomes more sophisticated.
let suffix = suffix.as_slice();
let (len, n) = (self.len(), suffix.len());
if n <= len {
let (head, tail) = self.split_at(len - n);
if tail == suffix {
return head;
}
}
self
}
/// Binary searches this slice for a given element.
/// If the slice is not sorted, the returned result is unspecified and
/// meaningless.

View file

@ -2426,6 +2426,83 @@ impl str {
suffix.strip_suffix_of(self)
}
/// Returns a string slice with the optional prefix removed.
///
/// If the string starts with the pattern `prefix`, returns the substring after the prefix.
/// Unlike [`strip_prefix`], this method always returns `&str` for easy method chaining,
/// instead of returning [`Option<&str>`].
///
/// If the string does not start with `prefix`, returns the original string unchanged.
///
/// The [pattern] can be a `&str`, [`char`], a slice of [`char`]s, or a
/// function or closure that determines if a character matches.
///
/// [`char`]: prim@char
/// [pattern]: self::pattern
/// [`strip_prefix`]: Self::strip_prefix
///
/// # Examples
///
/// ```
/// #![feature(trim_prefix_suffix)]
///
/// // Prefix present - removes it
/// assert_eq!("foo:bar".trim_prefix("foo:"), "bar");
/// assert_eq!("foofoo".trim_prefix("foo"), "foo");
///
/// // Prefix absent - returns original string
/// assert_eq!("foo:bar".trim_prefix("bar"), "foo:bar");
///
/// // Method chaining example
/// assert_eq!("<https://example.com/>".trim_prefix('<').trim_suffix('>'), "https://example.com/");
/// ```
#[must_use = "this returns the remaining substring as a new slice, \
without modifying the original"]
#[unstable(feature = "trim_prefix_suffix", issue = "142312")]
pub fn trim_prefix<P: Pattern>(&self, prefix: P) -> &str {
prefix.strip_prefix_of(self).unwrap_or(self)
}
/// Returns a string slice with the optional suffix removed.
///
/// If the string ends with the pattern `suffix`, returns the substring before the suffix.
/// Unlike [`strip_suffix`], this method always returns `&str` for easy method chaining,
/// instead of returning [`Option<&str>`].
///
/// If the string does not end with `suffix`, returns the original string unchanged.
///
/// The [pattern] can be a `&str`, [`char`], a slice of [`char`]s, or a
/// function or closure that determines if a character matches.
///
/// [`char`]: prim@char
/// [pattern]: self::pattern
/// [`strip_suffix`]: Self::strip_suffix
///
/// # Examples
///
/// ```
/// #![feature(trim_prefix_suffix)]
///
/// // Suffix present - removes it
/// assert_eq!("bar:foo".trim_suffix(":foo"), "bar");
/// assert_eq!("foofoo".trim_suffix("foo"), "foo");
///
/// // Suffix absent - returns original string
/// assert_eq!("bar:foo".trim_suffix("bar"), "bar:foo");
///
/// // Method chaining example
/// assert_eq!("<https://example.com/>".trim_prefix('<').trim_suffix('>'), "https://example.com/");
/// ```
#[must_use = "this returns the remaining substring as a new slice, \
without modifying the original"]
#[unstable(feature = "trim_prefix_suffix", issue = "142312")]
pub fn trim_suffix<P: Pattern>(&self, suffix: P) -> &str
where
for<'a> P::Searcher<'a>: ReverseSearcher<'a>,
{
suffix.strip_suffix_of(self).unwrap_or(self)
}
/// Returns a string slice with all suffixes that match a pattern
/// repeatedly removed.
///