Неожиданное поведение итератора при использовании peekable - PullRequest
0 голосов
/ 18 октября 2018
use std::str::Chars;

trait Extractor {
    fn peek_first(&mut self) -> Option<char>;
}

impl <'a> Extractor for Chars<'a> {
    fn peek_first(&mut self) -> Option<char> {
        let mut pk = self.peekable();
        pk.peek().and_then(|c| Some(*c))
    }
}

#[cfg(test)]
mod tests {
    use std::str::Chars;
    use super::Extractor;

    #[test]
    fn peek_first_test() {
        let mut iterator: Chars;
        iterator = "".chars();
        assert_eq!(iterator.peek_first(), None);
        assert_eq!(iterator.as_str(), "".to_string());

        iterator = "A".chars();
        assert_eq!(iterator.peek_first(), Some('A'));
        assert_eq!(iterator.as_str(), "A".to_string());

        iterator = "AB".chars();
        assert_eq!(iterator.peek_first(), Some('A'));
        assert_eq!(iterator.as_str(), "AB".to_string());
    }
} 

Я испытываю черты и хотел поставить peek_first () на итератор Chars.Как вы можете видеть, я использую peekable, чтобы получить peekable итератор от самоитератора.Я сделал тест, чтобы увидеть, не изменит ли peek_first () состояние самоитератора, но сделал это.Просматриваемый элемент изменяет базовый итератор и продвигает его.

Тест

assert_eq!(iterator.as_str(), "A".to_string());

завершается неудачей, так как iterator.as_str () возвращает пустую строку.

Это правильное поведение или нет?Я не смог найти это ни в одной документации по ржавчине.

1 Ответ

0 голосов
/ 18 октября 2018

Документация Iterator::peekable() явно документирует это поведение:

Обратите внимание, что базовый итератор все еще продвигается, когда peek вызывается впервые:чтобы извлечь следующий элемент, на базовом итераторе вызывается next, поэтому любые побочные эффекты (то есть что-либо кроме извлечения следующего значения) метода next будут иметь место.

, так как Peekable работает с произвольными итераторами, он может использовать только стандартный интерфейс итератора для просмотра следующего элемента, и единственный способ получить следующий элемент из универсального Iterator - это вызвать на нем next.

Я бы хотел добавить немного больше контекста в то, что происходит в вашем коде.Структура Peekable является адаптером итератора.Если у вас есть итератор iter, вы можете вызвать iter.peekable(), чтобы получить новый итератор, который поддерживает просмотр следующего элемента.Метод peekable() принимает значение self, что означает, что он использует исходный итератор.Таким образом, стандартный способ его использования - это код, подобный следующему:

let mut iter = "abc".chars().peekable();

Теперь iter является итератором с возможностью просмотра, и peeking не продвигает iter сам по себе, а только базовыйитератор, который обернут в Peekable и больше не доступен напрямую.

Однако в вашем коде вы создаете новую оболочку Peekable каждый раз, когда вызывается peek_first().Оболочка сбрасывается в конце peek_first().В вашей тестовой функции вы видите только базовый итератор, который продвигается каждый раз, как указано в документации.

Так почему же даже возможно сохранить доступ к базовому итератору, если peekable() занимает selfпо стоимости и потребляет это?Это из-за реализации переадресации черты Iterator для изменяемых ссылок на итераторы :

impl<'_, I> Iterator for &'_ mut I
where
    I: Iterator + ?Sized;

Метод peek_first() получает self по изменяемой ссылке, поэтому он можетне использовать базовый итератор.Вместо этого он использует реализацию перенаправления для изменяемых ссылок на итераторы и использует только изменяемую ссылку.

В качестве примечания, вы используете .and_then(|c| Some(*c)), чтобы превратить Option<&T> в Option<T>.Для этого есть специальный метод, который называется cloned().

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