使用Peekable时发生意外的迭代器行为

时间:2018-10-18 11:04:31

标签: rust

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从自迭代器中获取peekable迭代器。我进行了测试,看peek_first()是否不会改变自我迭代器的状态,但是会改变。偷看元素更改了基础迭代器并进行了改进。

测试

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

由于iterator.as_str()计算为空字符串而失败。

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

1 个答案:

答案 0 :(得分:2)

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

  

请注意,当第一次调用peek时,基础迭代器仍处于高级状态:为了检索下一个元素,在基础迭代器上调用next,因此产生了任何副作用(即,除了获取next方法的下一个值之外,其他任何事情都将发生。

由于Peekable适用于任意迭代器,因此它只能使用标准迭代器接口来窥视下一个元素,并且从通用Iterator获取下一个元素的唯一方法是调用{ {1}}。

我想在代码中添加更多内容。结构next是迭代器适配器。如果您有一个迭代器Peekable,则可以调用iter获得一个 new 迭代器,该迭代器支持在下一个元素上进行窥视。方法iter.peekable()按值取peekable(),这意味着它消耗了原始的迭代器。因此,使用它的标准方式是这样的代码:

self

现在let mut iter = "abc".chars().peekable(); 是一个可窥视的迭代器,并且窥视并不会使iter本身前进,而只会使底层的迭代器前进,该迭代器包装在iter中,而不是可以直接访问了。

但是,在您的代码中,每次调用Peekable时,都会创建一个新的Peekable包装器。包装器将放在peek_first()的末尾。在测试函数中,您只会看到底层的迭代器,该迭代器每次都会高级,如文档中所述。

那么,如果peek_first()取值peekable()并使用它,为什么还能保留对底层迭代器的访问?这是由于the forwarding implementation of the Iterator trait for mutable references to iterators

self

impl<'_, I> Iterator for &'_ mut I where I: Iterator + ?Sized; 方法通过可变引用接收peek_first(),因此它不能使用底层的迭代器。相反,它将转发实现用于对迭代器的可变引用,并且仅使用可变引用。

作为旁注,您可以使用self.and_then(|c| Some(*c))变成Option<&T>。有一个专用的方法,称为Option<T>