Efficient String trim

Viewed 197

I have a String value and I want to trim() it. I can do something like:

let trimmed = s.trim().to_string();

But that will always create a new String instance, even though in real life the string is much more likely to be already trimmed. In order to avoid the redundant new String creation, I could do something like this:

let ss = s.trim();
let trimmed = if ss.len() == s.len() { s } else { ss.to_string() };

But that is quite verbose. Is there a more concise way to do the above?

2 Answers

I can't think of a more concise way to do it. As it is, it seems maximally concise: You need to tell the compiler to trim the string, and then if the strings are the same length return the original string, otherwise make a new string. There isn't a "trim but return the original string if they're equal" method on String.

That said, you could make your own trait TrimOwned which had such a method, for example (implementation courtesy of StackOverflower):

trait TrimOwned {
    fn trim_owned(self) -> Self;
}

impl TrimOwned for String {
    fn trim_owned(self) -> Self {
        let s = self.trim();
        
        if s.len() == self.len() {
            self
        } else {
            s.to_string()
        }
    }
}

fn main() {
    let left  = "    left".to_string();
    let right = "right   ".to_string();
    let both  = "  both  ".to_string();

    println!("'{}'->'{}'", left.clone(),  left.trim_owned());
    println!("'{}'->'{}'", right.clone(), right.trim_owned());
    println!("'{}'->'{}'", both.clone(),  both.trim_owned());
}

Playground

Sorry, I cannot give you a more concise version. But I can tell you that there still is more room for premature optimization:

  • When trimming on the right, you only need to shorten the string, there is obviously no need for reallocating
  • You can't easily do the "shorten the string" trick when trimming to the left, since a string must always start at index 0 of its allocated area. But when you allocate a new string, you end up copying its contents, so you could as well just move the string content inside the already allocated area.

One nifty and reasonably fast way of implementing this is String::drain:

fn trim_owned(mut trim: String) -> String {
    trim.drain(trim.trim_end().len()..);
    trim.drain(..(trim.len() - trim.trim_start().len()));
    trim
}
Related