首页 > 解决方案 > 使用 peekable 时出现意外的迭代器行为

问题描述

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());
    }
} 

我正在尝试特征并想在 Chars 迭代器上放置一个 peek_first() 。正如你所看到的,我使用 peekable 从 self 迭代器中获取一个 peekable 迭代器。我做了一个测试,看看 peek_first() 是否不会改变自迭代器状态,但它确实改变了。peeked for 元素改变了底层迭代器并推进它。

考试

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

失败,因为 iterator.as_str() 评估为空字符串。

这是正确的行为吗?我在任何有关 rust 的文档中都找不到这个。

标签: rust

解决方案


Iterator::peekable()明确记录此行为的文档:

请注意,底层迭代器peek在第一次调用时仍然处于高级状态:为了检索下一个元素,next在底层迭代器上调用,因此该方法的任何副作用(即,除了获取下一个值之外的任何副作用)next都会​​发生.

由于Peekable适用于任意迭代器,它只能使用标准迭代器接口来查看下一个元素,而从泛型中获取下一个元素的唯一方法Iterator是调用next它。

我想为您的代码中发生的事情添加更多上下文。该结构Peekable是一个迭代器适配器。如果你有一个迭代器iter,你可以调用iter.peekable()来获取一个支持查看下一个元素的新迭代器。该方法按值peekable()获取self,这意味着它使用原始迭代器。所以使用它的标准方法是这样的代码:

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

Nowiter是一个 peekable 迭代器,并且 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))将 anOption<&T>转换为Option<T>. 有一个专门的方法,称为cloned().


推荐阅读