Итерация по всему файлу по одному символу за раз - PullRequest
1 голос
/ 06 марта 2020

Я новичок в 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`.

1 Ответ

1 голос
/ 07 марта 2020

Некоторые примечания:

  • Итератор Chars заимствует из строки, из которой он был создан. Таким образом, вы не можете удалить строку, пока итератор жив. Но это то, что происходит в вашем методе new(), переменная line, владеющая String, исчезает, а итератор, ссылающийся на нее, хранится в структуре.
  • Вы также можете попробовать сохранить текущую строку в структуре тогда он будет жить достаточно долго, но это не вариант - структура не может содержать ссылку на себя.
  • Можете ли вы сделать итератор char для строки, которая не хранит ссылка на строку? Да, возможно, например, сохраняя текущую позицию в строке как целое число - это не должно быть индексом символа, потому что символы могут иметь длину более одного байта, поэтому вам придется иметь дело с базовыми байтами самостоятельно (например, is_char_boundary(), чтобы взять следующую группу байтов, начиная с текущего индекса, который образует символ).
  • Есть ли более простой способ? Да, если производительность не имеет первостепенного значения, одним из решений является использование Vec IntoIterator экземпляра (который использует unsafe magi c для создания объекта, который раздает части самого себя):
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 экземпляр, и я бы не согласился.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...