Trim String in place?


#1

I wonder why no trim operation for String, that modify it by it self,
for example:

impl String {
  fn trim_right_mut(&mut self) { }
}

?

I’m asking in context of this:

let mut s: String = some_calculations();//allocation one
let s = s.trim().to_string();//allocation two

For me the second heap allocation is strange.
Why not reuse existing storage of s.
May be I should use some magic with iterators, like drain to reuse “allocation one” space?


#2

trim_right_mut can be fairly easily implemented using truncate


#3

One of the problems I can see is that trimming on the left in-place is going to be almost as expensive as calling s.trim().to_string(). Allocating memory is actually quite fast, and regardless you’re going to be copying the entire string (minus the leading whitespace) around.


#4

So malloc + memcpy is cheaper then memmove?


#5

I do benchmark, and looks like memove is faster then malloc + memcpy:

static SRC: &'static str = "  AAA BBB CCC DD       ";

#[bench]
fn bench_alloc_trim(b: &mut Bencher) {
    let src = SRC.to_string();
    b.iter(|| {
        test::black_box(src.trim().to_string());
    });
}

#[bench]
fn bench_inplace_trim(b: &mut Bencher) {
    let src = SRC.to_string();
    let trim = src.trim();
    let start_off = trim.as_ptr() as usize - src.as_str().as_ptr() as usize;
    let trim_len = trim.len();
    let mut src2 = src.clone();
    let mem = unsafe { src2.as_mut_vec() };
    b.iter(|| {
        test::black_box(src.trim());
        test::black_box(unsafe {
            ptr::copy(
                ((mem.as_ptr() as usize) + start_off) as *const u8,
                mem.as_mut_ptr(),
                trim_len,
            )
        });
    });
}

Results:

running 2 tests
test bench_alloc_trim   ... bench:          51 ns/iter (+/- 2)
test bench_inplace_trim ... bench:          32 ns/iter (+/- 1)