一次遍历整个文件一个字符

Iterate through a whole file one character at a time

我是 Rust 的新手,我对生命周期的概念感到困惑。我想创建一个结构,一次遍历一个字符的文件,但我 运行 遇到了需要生命周期的问题。我试图将它们添加到我认为应该添加的位置,但编译器不满意。这是我的代码:

struct Advancer<'a> {
    line_iter: Lines<BufReader<File>>,
    char_iter: Chars<'a>,
    current: Option<char>,
    peek: Option<char>,
}

impl<'a> Advancer<'a> {
    pub fn new(file: BufReader<File>) -> Result<Self, Error> {
        let mut line_iter = file.lines();
        if let Some(Ok(line)) = line_iter.next() {
            let char_iter = line.chars();

            let mut advancer = Advancer {
                line_iter,
                char_iter,
                current: None,
                peek: None,
            };

            // Prime the pump. Populate peek so the next call to advance returns the first char
            let _ = advancer.next();

            Ok(advancer)
        } else {
            Err(anyhow!("Failed reading an empty file."))
        }
    }

    pub fn next(&mut self) -> Option<char> {
        self.current = self.peek;
        if let Some(char) = self.char_iter.next() {
            self.peek = Some(char);
        } else {
            if let Some(Ok(line)) = self.line_iter.next() {
                self.char_iter = line.chars();
                self.peek = Some('\n');
            } else {
                self.peek = None;
            }
        }

        self.current
    }

    pub fn current(&self) -> Option<char> {
        self.current
    }

    pub fn peek(&self) -> Option<char> {
        self.peek
    }
}

fn main() -> Result<(), Error> {
    let file = File::open("input_file.txt")?;
    let file_buf = BufReader::new(file);
    let mut advancer = Advancer::new(file_buf)?;

    while let Some(char) = advancer.next() {
        print!("{}", char);
    }

    Ok(())
}

下面是编译器告诉我的内容:

error[E0515]: cannot return value referencing local variable `line`
  --> src/main.rs:37:13
   |
25 |             let char_iter = line.chars();
   |                             ---- `line` is borrowed here
...
37 |             Ok(advancer)
   |             ^^^^^^^^^^^^ returns a value referencing data owned by the current function

error[E0597]: `line` does not live long enough
  --> src/main.rs:49:34
   |
21 | impl<'a> Advancer<'a> {
   |      -- lifetime `'a` defined here
...
49 |                 self.char_iter = line.chars();
   |                 -----------------^^^^--------
   |                 |                |
   |                 |                borrowed value does not live long enough
   |                 assignment requires that `line` is borrowed for `'a`
50 |                 self.peek = Some('\n');
51 |             } else {
   |             - `line` dropped here while still borrowed

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0515, E0597.
For more information about an error, try `rustc --explain E0515`.
error: could not compile `advancer`.

一些注意事项:

  • Chars iterator 借用了创建它的字符串。因此,当迭代器处于活动状态时,您不能删除 String。但这就是您的 new() 方法中发生的情况,拥有 Stringline 变量消失,而引用它的迭代器存储在结构中。
  • 您也可以尝试将当前行存储在结构中,这样它就可以存活足够长的时间,但是 – 结构不能保存对自身的引用。
  • 你能否在不将引用存储到字符串中的字符串上创建一个字符迭代器?是的,可能,例如通过将字符串中的当前位置存储为整数——它不应该是 char 的索引,因为 char 的长度可能超过一个字节,因此您需要处理底层字节你自己(使用例如 is_char_boundary() 来获取从当前索引开始的下一堆字节,形成一个字符)。
  • 有没有更简单的方法?是的,如果性能不是最重要的,一个解决方案是利用 VecIntoIterator 实例(它使用 unsafe 魔法创建一个对象,该对象分发自己的一部分) :
let char_iter = file_buf.lines().flat_map(|line_res| {
    let line = line_res.unwrap_or(String::new());
    line.chars().collect::<Vec<_>>()
});

请注意,仅返回 line.chars() 会出现与第一点相同的问题。

您可能认为 String 应该有一个类似的 IntoIterator 实例,我不会不同意。