From 32cb8f1537a3c0948e01cb90c850d9f60e3139ed Mon Sep 17 00:00:00 2001 From: "Deven T. Corzine" Date: Tue, 10 Jun 2025 23:50:48 -0400 Subject: [PATCH] Add trim_prefix and trim_suffix for slice and str. 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. --- library/core/src/slice/mod.rs | 83 +++++++++++++++++++++++++++++++++++ library/core/src/str/mod.rs | 77 ++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) diff --git a/library/core/src/slice/mod.rs b/library/core/src/slice/mod.rs index 4f7e144088045..bdd83a2bd33c3 100644 --- a/library/core/src/slice/mod.rs +++ b/library/core/src/slice/mod.rs @@ -2763,6 +2763,89 @@ impl [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 + ?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 + ?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. diff --git a/library/core/src/str/mod.rs b/library/core/src/str/mod.rs index 41834793d22a5..5051b2288fd2b 100644 --- a/library/core/src/str/mod.rs +++ b/library/core/src/str/mod.rs @@ -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!("".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(&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!("".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(&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. ///