stringrusttrim

Is there a way to trim a String without allocating another one?


I have a file in the CSV format with a first column of data that represents item code optionally ended with "UNIUNI" or mixed case of these chars, loaded by means of a barcode reader. I need to trim away the last "UNI"s.

I wrote this function:

fn main() {
    // Ok: from "9846UNIUNI" to "9846"
    println!("{}", read_csv_rilev("9846UNIUNI".to_string()));
    
    // Wrong: from "9846uniuni" to "9846"
    println!("{}", read_csv_rilev("9846uniuni".to_string()));
}

fn read_csv_rilev(code: String) -> String {
    code
        //.to_uppercase() /*Unstable feature in Rust 1.1*/
        .trim_right_matches("UNI")
        .to_string()
}

The ideal function signature looks like:

fn read_csv_rilev(mut s: &String)

but probably an in-place action on a String is not a good idea. In fact, in the Rust standard library there isn't anything to do this excluding String::pop().


Solution

  • Is there a way to trim a String without allocating another one?

    Yes, you can use truncate to remove trailing parts of the string:

    const TRAILER: &'static str = "UNI";
    
    fn read_csv_rilev(s: &mut String) {
        while s.ends_with(TRAILER) {
            let len = s.len();
            let new_len = len.saturating_sub(TRAILER.len());
            s.truncate(new_len);
        }
    }
    
    fn main() {
        let mut code = "Hello WorldUNIUNIUNI".into();
        
        read_csv_rilev(&mut code);
        
        assert_eq!("Hello World", code);
    }
    

    You don't need to mess with the allocated string at all. You can use the same logic and make successive subslices of the string. This is basically how trim_right_matches works, but a bit less generic:

    const TRAILER: &'static str = "UNI";
    
    fn read_csv_rilev(mut s: &str) -> &str {
        while s.ends_with(TRAILER) {
            let len = s.len();
            let new_len = len.saturating_sub(TRAILER.len());
            s = &s[..new_len];
        }
        s
    }
    
    fn main() {
        let code = "Hello WorldUNIUNIUNI";
    
        let truncated = read_csv_rilev(code);
    
        assert_eq!("Hello World", truncated);
    }
    

    In general, I'd probably go with the second solution.