From 69b7f4255cf4bb33dff9d57b983dbed2a91b7bab Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Sat, 6 Dec 2025 08:47:20 +0100 Subject: [PATCH 1/3] Optimize `Extend for String` --- library/alloc/src/string.rs | 61 ++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index f5ba71c288334..4aaa83322f9f2 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -2203,6 +2203,55 @@ impl String { let slice = self.vec.leak(); unsafe { from_utf8_unchecked_mut(slice) } } + + /// SAFETY: `impl AsRef for S` must be stable + #[cfg(not(no_global_oom_handling))] + unsafe fn extend_many_chunked, I: IntoIterator>(&mut self, iter: I) { + let mut iter = iter.into_iter(); + + let mut repeat = true; + while repeat { + let chunk = match iter.next_chunk::<8>() { + Ok(chunk) => chunk.into_iter(), + Err(partial_chunk) => { + repeat = false; + partial_chunk + } + }; + + // SAFETY: the caller ensures that `impl AsRef for S` is stable + unsafe { self.extend_many(chunk.as_slice()) } + } + } + + /// SAFETY: `impl AsRef for S` must be stable + #[cfg(not(no_global_oom_handling))] + unsafe fn extend_many>(&mut self, vals: &[S]) { + let additional = vals.iter().fold(0usize, |a, s| a.saturating_add(s.as_ref().len())); + self.reserve(additional); + + let mut spare = self.vec.spare_capacity_mut().as_mut_ptr().cast_init(); + for val in vals { + let val = val.as_ref(); + // SAFETY: + // - `val` is a valid &str, so `val.as_ptr()` is valid + // for `val.len()` bytes and properly initialized. + // - `spare` points to valid spare capacity in the Vec + // with enough space for `val.len()` bytes. + // This is guaranteed because the caller ensures + // that `.as_ref()` is stable, and the saturating + // addition stops undercounting by overflow. + // - Both pointers are byte-aligned and the regions cannot overlap. + unsafe { + ptr::copy_nonoverlapping(val.as_ptr(), spare, val.len()); + spare = spare.add(val.len()); + } + } + + let new_len = self.vec.len() + additional; + // SAFETY: the elements have just been initialized + unsafe { self.vec.set_len(new_len) } + } } impl FromUtf8Error { @@ -2502,7 +2551,8 @@ impl<'a> Extend<&'a char> for String { #[stable(feature = "rust1", since = "1.0.0")] impl<'a> Extend<&'a str> for String { fn extend>(&mut self, iter: I) { - iter.into_iter().for_each(move |s| self.push_str(s)); + // SAFETY: `impl AsRef for &str` is stable + unsafe { self.extend_many_chunked(iter) } } #[inline] @@ -2515,7 +2565,8 @@ impl<'a> Extend<&'a str> for String { #[stable(feature = "box_str2", since = "1.45.0")] impl Extend> for String { fn extend>>(&mut self, iter: I) { - iter.into_iter().for_each(move |s| self.push_str(&s)); + // SAFETY: `impl AsRef for Box` is stable + unsafe { self.extend_many_chunked(iter) } } } @@ -2523,7 +2574,8 @@ impl Extend> for String { #[stable(feature = "extend_string", since = "1.4.0")] impl Extend for String { fn extend>(&mut self, iter: I) { - iter.into_iter().for_each(move |s| self.push_str(&s)); + // SAFETY: `impl AsRef for String` is stable + unsafe { self.extend_many_chunked(iter) } } #[inline] @@ -2536,7 +2588,8 @@ impl Extend for String { #[stable(feature = "herd_cows", since = "1.19.0")] impl<'a> Extend> for String { fn extend>>(&mut self, iter: I) { - iter.into_iter().for_each(move |s| self.push_str(&s)); + // SAFETY: `impl AsRef for Cow<'a, str>` is stable + unsafe { self.extend_many_chunked(iter) } } #[inline] From a87930f6e50a8af2202a051f911ed41926f7787b Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Sun, 7 Dec 2025 10:15:38 +0100 Subject: [PATCH 2/3] Specialize --- library/alloc/src/string.rs | 95 +++++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 25 deletions(-) diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index 4aaa83322f9f2..81aa6773f8d99 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -54,7 +54,7 @@ use core::ops::AddAssign; use core::ops::Bound::{Excluded, Included, Unbounded}; use core::ops::{self, Range, RangeBounds}; use core::str::pattern::{Pattern, Utf8Pattern}; -use core::{fmt, hash, ptr, slice}; +use core::{array, fmt, hash, ptr, slice}; #[cfg(not(no_global_oom_handling))] use crate::alloc::Allocator; @@ -2204,26 +2204,6 @@ impl String { unsafe { from_utf8_unchecked_mut(slice) } } - /// SAFETY: `impl AsRef for S` must be stable - #[cfg(not(no_global_oom_handling))] - unsafe fn extend_many_chunked, I: IntoIterator>(&mut self, iter: I) { - let mut iter = iter.into_iter(); - - let mut repeat = true; - while repeat { - let chunk = match iter.next_chunk::<8>() { - Ok(chunk) => chunk.into_iter(), - Err(partial_chunk) => { - repeat = false; - partial_chunk - } - }; - - // SAFETY: the caller ensures that `impl AsRef for S` is stable - unsafe { self.extend_many(chunk.as_slice()) } - } - } - /// SAFETY: `impl AsRef for S` must be stable #[cfg(not(no_global_oom_handling))] unsafe fn extend_many>(&mut self, vals: &[S]) { @@ -2552,7 +2532,7 @@ impl<'a> Extend<&'a char> for String { impl<'a> Extend<&'a str> for String { fn extend>(&mut self, iter: I) { // SAFETY: `impl AsRef for &str` is stable - unsafe { self.extend_many_chunked(iter) } + unsafe { self.extend_many_chunked(iter.into_iter()) } } #[inline] @@ -2566,7 +2546,7 @@ impl<'a> Extend<&'a str> for String { impl Extend> for String { fn extend>>(&mut self, iter: I) { // SAFETY: `impl AsRef for Box` is stable - unsafe { self.extend_many_chunked(iter) } + unsafe { self.extend_many_chunked(iter.into_iter()) } } } @@ -2575,7 +2555,7 @@ impl Extend> for String { impl Extend for String { fn extend>(&mut self, iter: I) { // SAFETY: `impl AsRef for String` is stable - unsafe { self.extend_many_chunked(iter) } + unsafe { self.extend_many_chunked(iter.into_iter()) } } #[inline] @@ -2589,7 +2569,7 @@ impl Extend for String { impl<'a> Extend> for String { fn extend>>(&mut self, iter: I) { // SAFETY: `impl AsRef for Cow<'a, str>` is stable - unsafe { self.extend_many_chunked(iter) } + unsafe { self.extend_many_chunked(iter.into_iter()) } } #[inline] @@ -2626,6 +2606,71 @@ impl<'a> Extend<&'a core::ascii::Char> for String { } } +#[cfg(not(no_global_oom_handling))] +trait ExtendManySpec { + /// SAFETY: `impl AsRef for S` must be stable + unsafe fn extend_many_chunked(&mut self, iter: I); +} + +#[cfg(not(no_global_oom_handling))] +impl ExtendManySpec for String +where + S: AsRef, + I: Iterator, +{ + default unsafe fn extend_many_chunked(&mut self, mut iter: I) { + let mut repeat = true; + while repeat { + let chunk = match iter.next_chunk::<8>() { + Ok(chunk) => chunk.into_iter(), + Err(partial_chunk) => { + repeat = false; + partial_chunk + } + }; + + // SAFETY: the caller ensures that `impl AsRef for S` is stable + unsafe { self.extend_many(chunk.as_slice()) } + } + } +} + +// TODO: specialization thinks that this is a conflicting implementation +/* +#[cfg(not(no_global_oom_handling))] +impl<'a, S> ExtendManySpec> for String +where + S: AsRef, +{ + unsafe fn extend_many_chunked(&mut self, iter: slice::Iter<'a, S>) { + // SAFETY: the caller ensures that `impl AsRef for S` is stable + unsafe { self.extend_many(iter.as_slice()) } + } +} +*/ + +#[cfg(not(no_global_oom_handling))] +impl ExtendManySpec> for String +where + S: AsRef, +{ + unsafe fn extend_many_chunked(&mut self, iter: array::IntoIter) { + // SAFETY: the caller ensures that `impl AsRef for S` is stable + unsafe { self.extend_many(iter.as_slice()) } + } +} + +#[cfg(not(no_global_oom_handling))] +impl ExtendManySpec> for String +where + S: AsRef, +{ + unsafe fn extend_many_chunked(&mut self, iter: vec::IntoIter) { + // SAFETY: the caller ensures that `impl AsRef for S` is stable + unsafe { self.extend_many(iter.as_slice()) } + } +} + /// A convenience impl that delegates to the impl for `&str`. /// /// # Examples From b6110e5d14f8b2c59979d9525fa0356f21f847d1 Mon Sep 17 00:00:00 2001 From: Paolo Barbolini Date: Sun, 7 Dec 2025 09:44:36 +0100 Subject: [PATCH 3/3] Convert consecutive `String::push_str` to `String::extend` --- compiler/rustc_ast/src/ast.rs | 6 ++--- .../src/assert/context.rs | 4 +-- library/alloc/src/borrow.rs | 26 ++++++++++--------- src/librustdoc/html/markdown.rs | 4 +-- src/librustdoc/html/render/print_item.rs | 3 +-- 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index db7cace49ae8f..5e82d4acbdb82 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -181,8 +181,7 @@ pub fn join_path_syms(path: impl IntoIterator>) -> St for sym in iter { let sym = *sym.borrow(); debug_assert_ne!(sym, kw::PathRoot); - s.push_str("::"); - s.push_str(sym.as_str()); + s.extend(["::", sym.as_str()]); } s } @@ -201,8 +200,7 @@ pub fn join_path_idents(path: impl IntoIterator>) -> S for ident in iter { let ident = *ident.borrow(); debug_assert_ne!(ident.name, kw::PathRoot); - s.push_str("::"); - s.push_str(&ident.to_string()); + s.extend(["::", &ident.to_string()]); } s } diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index 31855cbd4e6cc..96013e72186d0 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -334,9 +334,7 @@ impl<'cx, 'a> Context<'cx, 'a> { if self.paths.contains(&path_ident) { return; } else { - self.fmt_string.push_str(" "); - self.fmt_string.push_str(path_ident.as_str()); - self.fmt_string.push_str(" = {:?}\n"); + self.fmt_string.extend([" ", path_ident.as_str(), " = {:?}\n"]); let _ = self.paths.insert(path_ident); } let curr_capture_idx = self.capture_decls.len(); diff --git a/library/alloc/src/borrow.rs b/library/alloc/src/borrow.rs index aa973e0bb0240..c993df1b5bdaf 100644 --- a/library/alloc/src/borrow.rs +++ b/library/alloc/src/borrow.rs @@ -13,8 +13,6 @@ use core::ops::{Deref, DerefPure}; use Cow::*; use crate::fmt; -#[cfg(not(no_global_oom_handling))] -use crate::string::String; // FIXME(inference): const bounds removed due to inference regressions found by crater; // see https://github.com/rust-lang/rust/issues/147964 @@ -496,12 +494,14 @@ impl<'a> AddAssign<&'a str> for Cow<'a, str> { if self.is_empty() { *self = Cow::Borrowed(rhs) } else if !rhs.is_empty() { - if let Cow::Borrowed(lhs) = *self { - let mut s = String::with_capacity(lhs.len() + rhs.len()); - s.push_str(lhs); - *self = Cow::Owned(s); + match self { + Self::Borrowed(lhs) => { + *self = Cow::Owned([lhs, rhs].into_iter().collect()); + } + Self::Owned(lhs) => { + lhs.push_str(&rhs); + } } - self.to_mut().push_str(rhs); } } } @@ -513,12 +513,14 @@ impl<'a> AddAssign> for Cow<'a, str> { if self.is_empty() { *self = rhs } else if !rhs.is_empty() { - if let Cow::Borrowed(lhs) = *self { - let mut s = String::with_capacity(lhs.len() + rhs.len()); - s.push_str(lhs); - *self = Cow::Owned(s); + match self { + Self::Borrowed(lhs) => { + *self = Cow::Owned([&*lhs, &*rhs].into_iter().collect()); + } + Self::Owned(lhs) => { + lhs.push_str(&rhs); + } } - self.to_mut().push_str(&rhs); } } } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index a4d377432c914..d623c94ff622a 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -1645,9 +1645,7 @@ pub(crate) fn plain_text_from_events<'a>( match &event { Event::Text(text) => s.push_str(text), Event::Code(code) => { - s.push('`'); - s.push_str(code); - s.push('`'); + s.extend(["`", code, "`"]); } Event::HardBreak | Event::SoftBreak => s.push(' '), Event::Start(Tag::CodeBlock(..)) => break, diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 0c511738d7c8a..69a335fe678e2 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -2257,8 +2257,7 @@ pub(crate) fn compare_names(left: &str, right: &str) -> Ordering { pub(super) fn full_path(cx: &Context<'_>, item: &clean::Item) -> String { let mut s = join_path_syms(&cx.current); - s.push_str("::"); - s.push_str(item.name.unwrap().as_str()); + s.extend(["::", item.name.unwrap().as_str()]); s }