Skip to content

Commit eaf3cac

Browse files
authored
Rollup merge of rust-lang#134316 - zachs18:string_replace_in_place_rebase, r=joshtriplett
Add `String::replace_first` and `String::replace_last` Rebase of rust-lang#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 rust-lang#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::thread::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: rust-lang/libs-team#506
2 parents 798b363 + 271b10c commit eaf3cac

File tree

4 files changed

+97
-0
lines changed

4 files changed

+97
-0
lines changed

library/alloc/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@
142142
#![feature(slice_range)]
143143
#![feature(std_internals)]
144144
#![feature(str_internals)]
145+
#![feature(string_replace_in_place)]
145146
#![feature(temporary_niche_types)]
146147
#![feature(trusted_fused)]
147148
#![feature(trusted_len)]

library/alloc/src/string.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2090,6 +2090,67 @@ impl String {
20902090
unsafe { self.as_mut_vec() }.splice((start, end), replace_with.bytes());
20912091
}
20922092

2093+
/// Replaces the leftmost occurrence of a pattern with another string, in-place.
2094+
///
2095+
/// This method can be preferred over [`string = string.replacen(..., 1);`][replacen],
2096+
/// as it can use the `String`'s existing capacity to prevent a reallocation if
2097+
/// sufficient space is available.
2098+
///
2099+
/// # Examples
2100+
///
2101+
/// Basic usage:
2102+
///
2103+
/// ```
2104+
/// #![feature(string_replace_in_place)]
2105+
///
2106+
/// let mut s = String::from("Test Results: ❌❌❌");
2107+
///
2108+
/// // Replace the leftmost ❌ with a ✅
2109+
/// s.replace_first('❌', "✅");
2110+
/// assert_eq!(s, "Test Results: ✅❌❌");
2111+
/// ```
2112+
///
2113+
/// [replacen]: ../../std/primitive.str.html#method.replacen
2114+
#[cfg(not(no_global_oom_handling))]
2115+
#[unstable(feature = "string_replace_in_place", issue = "147949")]
2116+
pub fn replace_first<P: Pattern>(&mut self, from: P, to: &str) {
2117+
let range = match self.match_indices(from).next() {
2118+
Some((start, match_str)) => start..start + match_str.len(),
2119+
None => return,
2120+
};
2121+
2122+
self.replace_range(range, to);
2123+
}
2124+
2125+
/// Replaces the rightmost occurrence of a pattern with another string, in-place.
2126+
///
2127+
/// # Examples
2128+
///
2129+
/// Basic usage:
2130+
///
2131+
/// ```
2132+
/// #![feature(string_replace_in_place)]
2133+
///
2134+
/// let mut s = String::from("Test Results: ❌❌❌");
2135+
///
2136+
/// // Replace the rightmost ❌ with a ✅
2137+
/// s.replace_last('❌', "✅");
2138+
/// assert_eq!(s, "Test Results: ❌❌✅");
2139+
/// ```
2140+
#[cfg(not(no_global_oom_handling))]
2141+
#[unstable(feature = "string_replace_in_place", issue = "147949")]
2142+
pub fn replace_last<P: Pattern>(&mut self, from: P, to: &str)
2143+
where
2144+
for<'a> P::Searcher<'a>: core::str::pattern::ReverseSearcher<'a>,
2145+
{
2146+
let range = match self.rmatch_indices(from).next() {
2147+
Some((start, match_str)) => start..start + match_str.len(),
2148+
None => return,
2149+
};
2150+
2151+
self.replace_range(range, to);
2152+
}
2153+
20932154
/// Converts this `String` into a <code>[Box]<[str]></code>.
20942155
///
20952156
/// Before doing the conversion, this method discards excess capacity like [`shrink_to_fit`].

library/alloctests/tests/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#![feature(local_waker)]
3737
#![feature(str_as_str)]
3838
#![feature(strict_provenance_lints)]
39+
#![feature(string_replace_in_place)]
3940
#![feature(vec_deque_pop_if)]
4041
#![feature(vec_deque_truncate_front)]
4142
#![feature(unique_rc_arc)]

library/alloctests/tests/string.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,40 @@ fn test_replace_range_evil_end_bound() {
719719
assert_eq!(Ok(""), str::from_utf8(s.as_bytes()));
720720
}
721721

722+
#[test]
723+
fn test_replace_first() {
724+
let mut s = String::from("~ First ❌ Middle ❌ Last ❌ ~");
725+
s.replace_first("❌", "✅✅");
726+
assert_eq!(s, "~ First ✅✅ Middle ❌ Last ❌ ~");
727+
s.replace_first("🦀", "😳");
728+
assert_eq!(s, "~ First ✅✅ Middle ❌ Last ❌ ~");
729+
730+
let mut s = String::from("❌");
731+
s.replace_first('❌', "✅✅");
732+
assert_eq!(s, "✅✅");
733+
734+
let mut s = String::from("");
735+
s.replace_first('🌌', "❌");
736+
assert_eq!(s, "");
737+
}
738+
739+
#[test]
740+
fn test_replace_last() {
741+
let mut s = String::from("~ First ❌ Middle ❌ Last ❌ ~");
742+
s.replace_last("❌", "✅✅");
743+
assert_eq!(s, "~ First ❌ Middle ❌ Last ✅✅ ~");
744+
s.replace_last("🦀", "😳");
745+
assert_eq!(s, "~ First ❌ Middle ❌ Last ✅✅ ~");
746+
747+
let mut s = String::from("❌");
748+
s.replace_last::<char>('❌', "✅✅");
749+
assert_eq!(s, "✅✅");
750+
751+
let mut s = String::from("");
752+
s.replace_last::<char>('🌌', "❌");
753+
assert_eq!(s, "");
754+
}
755+
722756
#[test]
723757
fn test_extend_ref() {
724758
let mut a = "foo".to_string();

0 commit comments

Comments
 (0)