Rust中的字符串拼接

7
我试图通过for循环获取一个&str和另一个&str并将它们连接起来,以便在添加多个部分后使用新组合的字符串。下面是for循环的一般布局,但由于多个错误,我很难合并字符串。
for line in reader.lines() {
    let split_line = line.unwrap().split(",");
    let mut edited_line = "";

    for word in split_line {
        if !word.contains("substring") {
            let test_string = [edited_line, word].join(",");
            edited_line = &test_string;
        }
    }
    let _ = writeln!(outfile, "{}", edited_line).expect("Unable to write to file"); 
}

第一个错误:

error[E0716]: temporary value dropped while borrowed

运行上述代码时会出现以下问题:

第二个错误:

error[E0308]: mismatched types expected &str, found struct std::string::String

将test_string从edited_line的赋值语句中删除&时会发生什么?

注意: format!concat!宏也将产生错误2。
似乎如果我得到错误2并将std::string:String转换为&str,则会出现指示变量存活时间不足的错误。

我该如何构建由多个部分组成的字符串?


“edited_line = &test_string;” 的意思是什么?你是不是想说 “edited_like += test_string”? - tadman
2个回答

12
注意,Rust 有两种字符串类型:String&str(实际上还有更多,但在这里不相关)。
  • String 是一个拥有所有权(owned)的字符串,大小可以动态增长和缩小。
  • &str 是一个借用(borrowed)的字符串,是不可变的。

调用[edited_line,word].join(",")会创建一个新的String,它会分配在堆上。然后,edited_line = &test_string 借用了这个String并将其隐式转换为&str

问题在于,只要所有者(test_string)超出范围,内存就会被释放,但是借用的生命周期却比test_string更长。在 Rust 中,这是根本不可能的,因为否则会产生 use-after-free 错误。

正确而且最有效的做法是在循环外创建一个空的String,然后在循环中仅进行追加

let mut edited_line = String::new();

for word in split_line {
    if !word.contains("substring") {
        edited_line.push(',');
        edited_line.push_str(word);
    }
}
注意,结果字符串将以逗号开头,这可能不是所需的。为避免这种情况,您可以编写
let mut edited_line = String::new();

for word in split_line {
    if !word.contains("substring") {
        if !edited_line.is_empty() {
            edited_line.push(',');
        }
        edited_line.push_str(word);
    }
}

使用itertools crate可以更加优雅地完成此操作,它为迭代器提供了一个join方法:

use itertools::Itertools;

let edited_line: String = line
    .unwrap()
    .split(",")
    .filter(|word| !word.contains("substring"))
    .join(",");

2

let mut edited_line = ""; 使得 edited_line 成为一个带有静态生命周期的 &str 类型。

如果要将 edited_line 变成字符串,可以使用 .to_owned() 方法追加字符串,或者使用 String::new() 创建一个新的字符串:

let mut edited_line = String::new();
// Or
let mut edited_line = "".to_owned();

如果您不熟悉Rust的`String`和`str`之间的区别,请参阅此处

对于您的情况,最重要的是,您无法扩展&str,但可以扩展String


一旦您将edited_line切换为String,使用[edited_line,word].join(“,”)设置edited_line的方法就可以工作:

for line in reader.lines() {
    let split_line = line.unwrap().split(",");
    let mut edited_line = String::new();

    for word in split_line {
        if !word.contains("substring") {
            let test_string = [edited_line.as_str(), word].join(","); // Added .as_str() to edited_line
            edited_line = test_string; // Removed the & here
        }
    }
    let _ = writeln!(outfile, "{}", edited_line).expect("Unable to write to file"); 
}

Playground

然而,这种方法既不高效,也不人性化。而且它的结果是在每一行前加上一个,,这可能是无意的。


这里有一个替代方案,只使用一个String实例:

for line in reader.lines() {
    let split_line = line.unwrap().split(",");
    let mut edited_line = String::new();

    for word in split_line {
        if !word.contains("substring") {
            edited_line.push(',');
            edited_line.push_str(word);
        }
    }
    let _ = writeln!(outfile, "{}", edited_line).expect("Unable to write to file"); 
}

这仍然在每行前加上,字符。您可以通过在推送,之前检查edited_line是否为空来解决此问题。 Playground
第三个选项是将for循环改为迭代器:
for line in reader.lines() {
    let edited_line = line.split(",")
        .filter(|word| !word.contains("substring"))
        .collect::<Vec<&str>>() // Collecting allows us to use the join function.
        .join(",");
    let _ = writeln!(outfile, "{}", edited_line).expect("Unable to write to file"); 
}

游乐场

这样我们就可以按照预期使用join函数,整洁地消除每行开头的初始,


附注:如果您不确定每个变量的类型,建议使用像Intellij-rust这样的IDE,在编写时显示每个变量的类型提示。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接