如何从已拆分的文件中创建行迭代器?

MMa*_*eta 0 iterator rust

我有一个文件,需要逐行读取并分成两个句子,并用“=”分隔。我正在尝试使用迭代器,但我找不到如何在split. 文档说std::str::Split实现了该特征,但我仍然不知道如何使用它。

use std::{
    fs::File,
    io::{prelude::*, BufReader},
};

fn example(path: &str) {
    for line in BufReader::new(File::open(path).expect("Failed at opening file.")).lines() {
        let words = line.unwrap().split("="); //need to make this an iterable
    }
}
Run Code Online (Sandbox Code Playgroud)

我如何使用我知道已经实现为 split 之类的特征?

Pri*_*six 7

正如@Mateen 评论的那样split已经返回一个可迭代的。要解决生命周期问题,请在调用 之前将 返回的值保存unwrap()到变量中split

我将在这里尝试解释生命周期问题。

首先,查看函数签名确实很有帮助。

pub fn unwrap(self) -> T
Run Code Online (Sandbox Code Playgroud)
pub fn split<'a, P: Pattern<'a>>(&'a self, pat: P) -> Split<'a, P>
Run Code Online (Sandbox Code Playgroud)

unwrap非常简单,它获取自身的所有权并返回内部值。

split看起来很吓人,但其实并不太难,'a只是生命周期的一个名字,它只是说明了返回值可以使用多长时间。在这种情况下,这意味着两个输入参数的生存时间必须至少与返回值一样长。

//                   Takes by reference, no ownership change
//                               v
pub fn split<'a, P: Pattern<'a>>(&'a self, pat: P) -> Split<'a, P>
//           ^              ^      ^                         ^
//           |              |--|---|                         |
// This just declares a name.  |                             |
//                             |                             |
//           Both of these values must last longer than -----|
Run Code Online (Sandbox Code Playgroud)

这是因为split不复制任何字符串,它只是指向原始字符串上发生分割的位置。如果原始字符串由于某种原因被删除,则Split不会指向无效数据。

变量的生命周期(除非所有权被传递给其他东西)持续到它超出范围为止,如果它}被命名(例如 with let),则在结束时或者在行的末尾 /;

这就是您的代码中存在生命周期问题的原因:

for line in std::io::BufReader::new(std::fs::File::open(path).expect("Failed at opening file.")).lines() {
    let words = line
        .unwrap() // <--- Unwrap consumes `line`, `line` can not be used after calling unwrap(),
        .split("=") // Passed unwrap()'s output to split as a reference
        ; //<-- end of line, unwrap()'s output is dropped due to it not being saved to a variable, the result of split now points to nothing, so the compiler complains.
}
Run Code Online (Sandbox Code Playgroud)

解决方案

保存返回值unwrap()

for line in std::io::BufReader::new(std::fs::File::open("abc").expect("Failed at opening file.")).lines() {
    let words = line.unwrap();
    let words_split = words.split("=");
} // <--- `word`'s lifetime ends here, but there is no lifetime issues since `words_split` also ends here.
Run Code Online (Sandbox Code Playgroud)

如果需要,您可以重命名words_splitwords以隐藏原始变量,以免变量名称混乱,这也不会导致问题,因为隐藏变量不会立即删除,而是在其原始范围末尾删除。

或者

str您可以将每个切片复制到它自己的字符串中,而不是使用类型为 的迭代器(所有这些迭代器都只是指向原始字符串的奇特指针),从而消除了对将原始字符串保留在范围内的依赖。

在您的情况下几乎可以肯定没有理由这样做,因为复制每个切片需要更多的处理能力和更多的内存,但 Rust 为您提供了这种控制。

let words = line
    .unwrap()
    .split("=")
    .map(|piece|
        piece.to_owned() // <--- This copies all the characters in the str into it's own String.
    ).collect::<Vec<String>>()
    ; // <--- unwrap()'s output dropped here, but it doesn't matter since the pieces no longer points to the original line string.

let words_iterator = words.iter();
Run Code Online (Sandbox Code Playgroud)

collect给你错误,cannot infer type因为你没有说明你想要收集什么,要么使用上面的turbofish语法,要么在wordsie上说明let words: Vec<String> = ...

您必须致电collect,因为map除非您使用它,否则不会执行任何操作,但这超出了本答案的范围。