A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://github.com/rust-lang/libs-team/issues/597 below:

Add trim_prefix and trim_suffix methods to str · Issue #597 · rust-lang/libs-team · GitHub

Proposal

Add trim_prefix and trim_suffix methods to str which remove at most one occurrence of a specified prefix or suffix, always returning &str rather than Option<&str>.

Problem statement

Currently, Rust's string API has a gap between two existing method families:

There's no method that removes at most one occurrence of a prefix/suffix while always returning a string slice. This breaks method chaining entirely and forces developers to write verbose code for a common pattern:

let result = if let Some(stripped) = s.strip_prefix(prefix) {
    stripped
} else {
    s
};

This can be simplified somewhat by using unwrap_or(), but still this remains more verbose and awkward than necessary, and the source variable still needs to be used twice:

let result = s.strip_prefix(prefix).unwrap_or(s);
Motivating examples or use cases

For example, suppose a string contains a URL, but it might also have leading/trailing whitespace, possible open and/or closing angle brackets and possibly more leading/trailing whitespace outside any angle brackets. Here are a number of variations matching that pattern:

let s = " < https://example.com/ > ";
let s =  "< https://example.com/ >";
let s =  " <https://example.com/> ";
let s =   "<https://example.com/>";
let s =   "<https://example.com/";
let s =   "<https://example.com/";
let s =    "https://example.com/>";
let s =   " https://example.com/ ";
let s =    "https://example.com/";

The URL could be extracted from any of these strings using strip_prefix and strip_suffix, but it is awkward, requiring several steps with variables for each step:

let s = " < https://example.com/ > ";

let s = s.trim();
let s = s.strip_prefix('<').unwrap_or(s);
let s = s.strip_suffix('>').unwrap_or(s);
let result = s.trim();

assert_eq!(result, "https://example.com/");

There is no easy way to accomplish the same thing with a simple method chain.

Solution sketch

My proposal is to add new trim_prefix and trim_suffix methods to str, which would work similarly to the strip_prefix and strip_suffix methods, but they would always return &str instead of Option<&str>, allowing easy method chaining. This way, the multi-step process above could be replaced with the method chain .trim().trim_prefix('<').trim_suffix('>').trim(), which is much simpler and easier to read and understand:

assert_eq!(" < https://example.com/ > ".trim().trim_prefix('<').trim_suffix('>').trim(), "https://example.com/");
assert_eq!( "< https://example.com/ >" .trim().trim_prefix('<').trim_suffix('>').trim(), "https://example.com/");
assert_eq!( " <https://example.com/> " .trim().trim_prefix('<').trim_suffix('>').trim(), "https://example.com/");
assert_eq!(  "<https://example.com/>"  .trim().trim_prefix('<').trim_suffix('>').trim(), "https://example.com/");
assert_eq!(  "<https://example.com/"   .trim().trim_prefix('<').trim_suffix('>').trim(), "https://example.com/");
assert_eq!(  "<https://example.com/"   .trim().trim_prefix('<').trim_suffix('>').trim(), "https://example.com/");
assert_eq!(   "https://example.com/>"  .trim().trim_prefix('<').trim_suffix('>').trim(), "https://example.com/");
assert_eq!(  " https://example.com/ "  .trim().trim_prefix('<').trim_suffix('>').trim(), "https://example.com/");
assert_eq!(   "https://example.com/"   .trim().trim_prefix('<').trim_suffix('>').trim(), "https://example.com/");

These methods would complement the existing string manipulation methods by providing a middle ground between the fallible strip_prefix/strip_suffix methods and the greedy trim_start_matches/trim_end_matches methods.

Method signatures
impl str {
    pub fn trim_prefix<P>(&self, prefix: P) -> &str
    where
        P: Pattern,

    pub fn trim_suffix<P>(&self, suffix: P) -> &str
    where
        P: Pattern,
        <P as Pattern>::Searcher<'a>: for<'a> ReverseSearcher<'a>,
}
Behavior specification Implementation
impl str {
    /// 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 a string slice instead of returning [`Option`].
    ///
    /// 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
    ///
    /// ```
    /// // 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");
    /// ```
    #[must_use = "this returns the remaining substring as a new slice, \
                  without modifying the original"]
    #[unstable(feature = "trim_prefix_suffix", issue = "none")]
    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 a string slice instead of returning [`Option`].
    ///
    /// 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
    ///
    /// ```
    /// // 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");
    /// ```
    #[must_use = "this returns the remaining substring as a new slice, \
                  without modifying the original"]
    #[unstable(feature = "trim_prefix_suffix", issue = "none")]
    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)
    }
}
Examples
// String literals
assert_eq!("hello world".trim_prefix("hello"), " world");
assert_eq!("hello world".trim_prefix("hi"), "hello world");
assert_eq!("hello world".trim_suffix("world"), "hello ");
assert_eq!("hello world".trim_suffix("universe"), "hello world");

// Characters
assert_eq!("xhello".trim_prefix('x'), "hello");
assert_eq!("hellox".trim_suffix('x'), "hello");

// Empty prefix/suffix
assert_eq!("hello".trim_prefix(""), "hello");
assert_eq!("hello".trim_suffix(""), "hello");

// Multiple occurrences (only first/last is removed)
assert_eq!("aaahello".trim_prefix('a'), "aahello");
assert_eq!("helloaaa".trim_suffix('a'), "helloaa");
Drawbacks Alternatives
  1. Extension trait in an external crate: This works but fragments the ecosystem and doesn't provide the discoverability of standard library methods.

  2. Alternative naming: trim_start_match/trim_end_match names would follow the pattern of existing trim_start_matches/trim_end_matches methods, using singular vs plural to distinguish behavior. However, this was rejected because:

  3. Generic over removal count: A method that could remove N occurrences was considered too complex for the common use case.

The trim_prefix/trim_suffix naming follows established conventions:

Why not just use value.strip_prefix().unwrap_or(value)?

While the unwrap_or() pattern works for simple cases, it has significant drawbacks:

  1. Poor method chaining: The unwrap_or() approach breaks fluent interfaces entirely.
// Clean, readable chaining with proposed methods:
let result = value.trim().trim_prefix(prefix).trim_suffix(suffix).trim();

// Current approach - chaining is impossible:
let result = value.trim();
let result = result.strip_prefix(prefix).unwrap_or(result);
let result = result.strip_suffix(suffix).unwrap_or(result);
let result = result.trim();

// Attempting to chain with current methods doesn't work:
let trimmed = value.trim();
let result = trimmed
    .strip_prefix(prefix).unwrap_or(trimmed)
    .strip_suffix(suffix).unwrap_or(???)  // Can't reference intermediate values
    .trim();
  1. Verbosity: Requires storing intermediate results and repeating variable names.
  2. Unclear intent: The unwrap_or() pattern doesn't clearly communicate "remove if present, otherwise unchanged".
Links and related work

Many string processing libraries in other languages provide similar functionality:

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

Second, if there's a concrete solution:

kennytm, clarfonthey, hanna-kruppe, bbb651, wmstack and 1 morebbb651 and Scripter17


RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4