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?
CodePudding user response:
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());
}
CodePudding user response:
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. (Sadly, this isn't cheap in safe Rust.)
fn trim_owned(trim: String) -> String {
// rfind would be cleaner, but this is is consise.
let end_trimmed = trim.trim_end().len();
if end_trimmed == 0 {
return String::new(); // is guaranteed to not allocate. And you'll have to dealloc the empty string at some point...
}
let start_trimmed = trim.len() - trim.trim_start().len();
// Moving needs to happen on bytes, since strings must always be valid utf-8
let mut bytes = trim.into_bytes();
if start_trimmed != 0 {
for (from, to) in (start_trimmed..end_trimmed).zip(0..) {
bytes[to] = bytes[from];
}
}
// You could do this in safe rust:
// bytes.truncate()
// String::from_bytes().unwrap()
// but I doubt it would be faster, with the additional UTF-8 check
unsafe {
bytes.set_len(end_trimmed - start_trimmed);
String::from_utf8_unchecked(bytes)
}
}
Of course, this is quite a nasty bit of code, you'd have to thoroughly test (and benchmark) it.
