Getting a `String` from a slice without reallocation

I ended up with a function creating a new String on which I might want to trim some characters.

fn doing_stuff() -> String {
  let s: String = some_function();
  s.trim_end().to_string()
}

From what I understand, this .to_string() will always trigger a new allocation. But in this case, I know the new String fits in the already allocated memory for s. However, I couldn't find an easy way for that. The solution I came out with was arguably less readable (since I do actually trim the string, so using the trim function seems fine).

```rust
fn doing_stuff() -> String {
  let mut s: String = some_function();
  if s.ends_with(' ') {
    s.truncate(s.len() - 1);
  }
  s
}

From what I can read, .truncate() does not reallocate... but I don't see mention of trim anymore in my code: sad to not be able to use the already existing trimming functions. Also, my code might panic since .truncate must respect char boundaries.

Did I miss something? It feels like it should be easy to go from a String to a slice, then back to the same String (just changing the value of the length).

let len = s.trim_end().len();
s.truncate(len);

That's only possible if you cut back from the end of the string — in which case, this is truncate().

If you cut off the beginning, then you can't keep the allocation, because allocations can't change base address.

If you want to keep the allocation and truncate from the left, then write a newtype around String that manages the buffer by-value but derefs to only a portion of the String.

3 Likes

Through the magic of two-phase-borrows, you’re even allowed to write what @H2CO3 suggested in a single line, as s.truncate(s.trim_end().len());.

In case truncating the front comes up, note that it’s not entirely impossible, but it would need to involve shifting all the string data around, which is probably only marginally cheaper than creating a new String. The relevant standard library API that can do such operations is String::replace_range, e.g. .replace_range(..n, "") will remove the first n bytes of a String (by shifting the remaining data further to the front).

1 Like

I intentionally avoided that :stuck_out_tongue: (following recent discussions about equivalent unsafe).

1 Like

Why is there never any love for String::drain() when I see this sort of thing discussed? Is it less performant than replace_range(..n, "") in some way?

2 Likes

No, drain might be better, I just somehow forgot about it :innocent:

Perfect, exactly what I was looking for. And thanks for the extra information about trimming on the left, the replace_range and drain, always nice to learn more things than what you're looking for.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.