有没有办法 trim 一个字符串而不分配另一个字符串?

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

我有一个 CSV 格式的文件,其中第一列数据代表项目代码,可选择以 "UNIUNI" 结尾或这些字符的大小写混合,通过条形码 reader 加载。我需要 trim 离开最后 "UNI" 秒。

我写了这个函数:

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()
}

理想的函数签名如下所示:

fn read_csv_rilev(mut s: &String)

但对 String 执行就地操作可能不是一个好主意。事实上,在 Rust 标准库中,除了 String::pop().

之外,没有任何东西可以做到这一点

but probably an in-place action on a String is not a good idea.

bindingmut s: &String 中是可变的,而不是字符串本身。如果你想改变字符串本身,你会选择 s: &mut String

也就是说,我认为标准库中没有任何东西可以做到这一点。

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

是的,您可以使用 truncate 删除字符串的尾部:

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);
}

您根本不需要弄乱分配的字符串。您可以使用相同的逻辑并制作字符串的连续子片段。这基本上就是 trim_right_matches 的工作原理,但不太通用:

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);
}

一般来说,我可能会选择第二种解决方案。

另一个解决方案是使用 owning_ref crate,它可以让你 return 同时 &str 和它的支持 String

extern crate owning_ref;
use owning_ref::StringRef;

fn read_csv_rilev(code: String) -> StringRef {
    StringRef::new(code).map(|s| s.trim_right_matches("UNI"))
}

我知道这是旧的,但有一个很好的两行,trim_right_matches 现在已弃用,但是 trim_end_matches() returns 一个 &str 与你想要的长度

fn read_csv_rilev(code: &mut String) {
        // code.to_uppercase();
        let l = code.trim_end_matches("UNI").len();
        code.truncate(l);
}