Rollup merge of #134316 - zachs18:string_replace_in_place_rebase, r=joshtriplett

Add `String::replace_first` and `String::replace_last`

Rebase of #97977 (cc `@WilliamVenner)`

> Convenience methods that use `match_indices` and `replace_range` to efficiently replace a substring in a string without reallocating, if capacity (and the implementation of `Vec::splice`) allows.

The intra-doc link to `str::replacen` is a direct url-based link to `str::replacen` in `std`'s docs to work around #98941. This means that when building only `alloc`'s docs (and not `std`'s), it will be a broken link. There is precedent for this e.g. in [`core::hint::spin_loop`](https://doc.rust-lang.org/nightly/src/core/hint.rs.html#214) which links to `std:🧵:yield_now` using a [url-based link](https://github.com/rust-lang/rust/blob/master/library/core/src/hint.rs#L265) and thus is a dead link when only building `core`'s docs.

ACP: https://github.com/rust-lang/libs-team/issues/506
This commit is contained in:
Jacob Pratt 2025-10-23 01:22:05 -04:00 committed by GitHub
commit 4491efbc82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 97 additions and 0 deletions

View file

@ -85,6 +85,7 @@
//
// Library features:
// tidy-alphabetical-start
#![cfg_attr(not(no_global_oom_handling), feature(string_replace_in_place))]
#![feature(alloc_layout_extra)]
#![feature(allocator_api)]
#![feature(array_into_iter_constructors)]

View file

@ -2090,6 +2090,67 @@ impl String {
unsafe { self.as_mut_vec() }.splice((start, end), replace_with.bytes());
}
/// Replaces the leftmost occurrence of a pattern with another string, in-place.
///
/// This method can be preferred over [`string = string.replacen(..., 1);`][replacen],
/// as it can use the `String`'s existing capacity to prevent a reallocation if
/// sufficient space is available.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// #![feature(string_replace_in_place)]
///
/// let mut s = String::from("Test Results: ❌❌❌");
///
/// // Replace the leftmost ❌ with a ✅
/// s.replace_first('❌', "✅");
/// assert_eq!(s, "Test Results: ✅❌❌");
/// ```
///
/// [replacen]: ../../std/primitive.str.html#method.replacen
#[cfg(not(no_global_oom_handling))]
#[unstable(feature = "string_replace_in_place", issue = "147949")]
pub fn replace_first<P: Pattern>(&mut self, from: P, to: &str) {
let range = match self.match_indices(from).next() {
Some((start, match_str)) => start..start + match_str.len(),
None => return,
};
self.replace_range(range, to);
}
/// Replaces the rightmost occurrence of a pattern with another string, in-place.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// #![feature(string_replace_in_place)]
///
/// let mut s = String::from("Test Results: ❌❌❌");
///
/// // Replace the rightmost ❌ with a ✅
/// s.replace_last('❌', "✅");
/// assert_eq!(s, "Test Results: ❌❌✅");
/// ```
#[cfg(not(no_global_oom_handling))]
#[unstable(feature = "string_replace_in_place", issue = "147949")]
pub fn replace_last<P: Pattern>(&mut self, from: P, to: &str)
where
for<'a> P::Searcher<'a>: core::str::pattern::ReverseSearcher<'a>,
{
let range = match self.rmatch_indices(from).next() {
Some((start, match_str)) => start..start + match_str.len(),
None => return,
};
self.replace_range(range, to);
}
/// Converts this `String` into a <code>[Box]<[str]></code>.
///
/// Before doing the conversion, this method discards excess capacity like [`shrink_to_fit`].

View file

@ -36,6 +36,7 @@
#![feature(local_waker)]
#![feature(str_as_str)]
#![feature(strict_provenance_lints)]
#![feature(string_replace_in_place)]
#![feature(vec_deque_pop_if)]
#![feature(vec_deque_truncate_front)]
#![feature(unique_rc_arc)]

View file

@ -719,6 +719,40 @@ fn test_replace_range_evil_end_bound() {
assert_eq!(Ok(""), str::from_utf8(s.as_bytes()));
}
#[test]
fn test_replace_first() {
let mut s = String::from("~ First ❌ Middle ❌ Last ❌ ~");
s.replace_first("", "✅✅");
assert_eq!(s, "~ First ✅✅ Middle ❌ Last ❌ ~");
s.replace_first("🦀", "😳");
assert_eq!(s, "~ First ✅✅ Middle ❌ Last ❌ ~");
let mut s = String::from("");
s.replace_first('❌', "✅✅");
assert_eq!(s, "✅✅");
let mut s = String::from("");
s.replace_first('🌌', "");
assert_eq!(s, "");
}
#[test]
fn test_replace_last() {
let mut s = String::from("~ First ❌ Middle ❌ Last ❌ ~");
s.replace_last("", "✅✅");
assert_eq!(s, "~ First ❌ Middle ❌ Last ✅✅ ~");
s.replace_last("🦀", "😳");
assert_eq!(s, "~ First ❌ Middle ❌ Last ✅✅ ~");
let mut s = String::from("");
s.replace_last::<char>('❌', "✅✅");
assert_eq!(s, "✅✅");
let mut s = String::from("");
s.replace_last::<char>('🌌', "");
assert_eq!(s, "");
}
#[test]
fn test_extend_ref() {
let mut a = "foo".to_string();