Skip to content

Commit 67944d6

Browse files
committed
Auto merge of rust-lang#149694 - lolbinarycat:alloc-extend-slice-of-str-spec, r=Mark-Simulacrum
alloc: specialize String::extend for slices of str Here's a small optimization via specialization that can potentially eliminate a fair few reallocations when building strings using specific patterns, unsure if this justifies the use of unsafe, but I had the code implemented from rust-lang#148604, so I thought it was worth submitting to see.
2 parents 56f24e0 + 38ab135 commit 67944d6

File tree

1 file changed

+46
-1
lines changed

1 file changed

+46
-1
lines changed

library/alloc/src/string.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ use core::iter::FusedIterator;
4747
#[cfg(not(no_global_oom_handling))]
4848
use core::iter::from_fn;
4949
#[cfg(not(no_global_oom_handling))]
50+
use core::num::Saturating;
51+
#[cfg(not(no_global_oom_handling))]
5052
use core::ops::Add;
5153
#[cfg(not(no_global_oom_handling))]
5254
use core::ops::AddAssign;
@@ -1100,6 +1102,23 @@ impl String {
11001102
self.vec.extend_from_slice(string.as_bytes())
11011103
}
11021104

1105+
#[cfg(not(no_global_oom_handling))]
1106+
#[inline]
1107+
fn push_str_slice(&mut self, slice: &[&str]) {
1108+
// use saturating arithmetic to ensure that in the case of an overflow, reserve() throws OOM
1109+
let additional: Saturating<usize> = slice.iter().map(|x| Saturating(x.len())).sum();
1110+
self.reserve(additional.0);
1111+
let (ptr, len, cap) = core::mem::take(self).into_raw_parts();
1112+
unsafe {
1113+
let mut dst = ptr.add(len);
1114+
for new in slice {
1115+
core::ptr::copy_nonoverlapping(new.as_ptr(), dst, new.len());
1116+
dst = dst.add(new.len());
1117+
}
1118+
*self = String::from_raw_parts(ptr, len + additional.0, cap);
1119+
}
1120+
}
1121+
11031122
/// Copies elements from `src` range to the end of the string.
11041123
///
11051124
/// # Panics
@@ -2486,7 +2505,7 @@ impl<'a> Extend<&'a char> for String {
24862505
#[stable(feature = "rust1", since = "1.0.0")]
24872506
impl<'a> Extend<&'a str> for String {
24882507
fn extend<I: IntoIterator<Item = &'a str>>(&mut self, iter: I) {
2489-
iter.into_iter().for_each(move |s| self.push_str(s));
2508+
<I as SpecExtendStr>::spec_extend_into(iter, self)
24902509
}
24912510

24922511
#[inline]
@@ -2495,6 +2514,32 @@ impl<'a> Extend<&'a str> for String {
24952514
}
24962515
}
24972516

2517+
#[cfg(not(no_global_oom_handling))]
2518+
trait SpecExtendStr {
2519+
fn spec_extend_into(self, s: &mut String);
2520+
}
2521+
2522+
#[cfg(not(no_global_oom_handling))]
2523+
impl<'a, T: IntoIterator<Item = &'a str>> SpecExtendStr for T {
2524+
default fn spec_extend_into(self, target: &mut String) {
2525+
self.into_iter().for_each(move |s| target.push_str(s));
2526+
}
2527+
}
2528+
2529+
#[cfg(not(no_global_oom_handling))]
2530+
impl SpecExtendStr for [&str] {
2531+
fn spec_extend_into(self, target: &mut String) {
2532+
target.push_str_slice(&self);
2533+
}
2534+
}
2535+
2536+
#[cfg(not(no_global_oom_handling))]
2537+
impl<const N: usize> SpecExtendStr for [&str; N] {
2538+
fn spec_extend_into(self, target: &mut String) {
2539+
target.push_str_slice(&self[..]);
2540+
}
2541+
}
2542+
24982543
#[cfg(not(no_global_oom_handling))]
24992544
#[stable(feature = "box_str2", since = "1.45.0")]
25002545
impl<A: Allocator> Extend<Box<str, A>> for String {

0 commit comments

Comments
 (0)