Rust进阶-用之即弃的iterator

iterator在Rust或者Pytho等多种语言里都只能做一次迭代。比如在Rust中迭代到容器末尾后就会返回一个None此时再继续遍历也没有意义了,同时也没有提供方法可以重置。只能用之即弃了。

这个特性在std::io::Lines表现十分典型。来看下面代码:

fn compare(src : &str, des : &str){
    let f = File::open(src);
    let mut f = match f {
        Ok(file) => file,
        Err(e) => return,
    };

    let f_des = File::open(des);
    let mut f_des = match f_des {
        Ok(file) => file,
        Err(e) => return,
    };
    let buf_src = BufReader::new(&f).lines();

    for src_line in buf_src{
        let src = src_line.unwrap();
        
        let buf_des = BufReader::new(&f_des).lines();
        for des_line in buf_des{
            //比较每行内容
            print!("src line: {}   ", src);
            println!("des line: {}", &des_line.unwrap());
        }
    }
}

上面代码打开了两个文件,并且比较文件每行内容的差异。分别读取两个文件后,通过for循环嵌套,实现逐行对比。感觉很正常,编译通过,运行... 然鹅出问题了。

src line: src0   des line: des0
src line: src0   des line: des1
src line: src0   des line: des2
src line: src0   des line: des3

和期望结果不同,内层的循环只循环了一次。

为了找出原因,我们首先来查看Lines的定义:

impl<B: > for <B>

原来实现的是Iterator接口。

这意味着内层循环在迭代器移动容器末尾后,当外层循环在执行到内层时,迭代器已是末尾,所以不再执行内层循环。

如何解决这个问题?

可以通过Vec来间接避免迭代器用之即弃的问题。修改下代码:

fn compare1(src : &str, des : &str){
    let f = File::open(src);
    let mut f = match f {
        Ok(file) => file,
        Err(e) => return,
    };

    let f_des = File::open(des);
    let mut f_des = match f_des {
        Ok(file) => file,
        Err(e) => return,
    };

    let buf_des = BufReader::new(&f_des).lines();
    let buf_src = BufReader::new(&f).lines();
    let mut vec_des = Vec::new();
    for des_line in buf_des{
        vec_des.push(des_line.unwrap());
    }

    for src_line in buf_src{
        let src = src_line.unwrap();
        //print!("src line: {}   ", src);
        for des_line in &vec_des{
            //比较每行内容
            print!("src line: {}   ", src);
            println!("des line: {}", &des_line);
        }
    }
}

为什么使用了vec就解决了问题,吃个鸭脖,我们继续来分析问题。

=======================================路过的分割线=============================================

为了找到原因,还是需要从定义出发,找到vec定义,与迭代器相关Vec实现了trait IntoIterator:

impl<T> for <T>

继续查找IntoIterator就会发现这个东东是对Iterator的包装。

pub trait IntoIterator where Self:::: == Self:: {
    type Item;
    type IntoIter: ;
    fn (self) -> Self::;
}

for循环中通过intoIterator返回一个迭代器,对Vec的循环等价于

let result = match IntoIterator::into_iter(&v) {
            mut iter => loop {
                match iter.next() {
                    Some(x) => { print!("{}", x); }
                    None => break,
                }
            },
        };

这样保证了即使外层嵌套了循环,内层执行时都是重新生成一个迭代器,从而避免了迭代器用之即弃的问题。

以上个人观点,如果有错误请不吝指教。欢迎讨论,共同学习。

参考资料:

经验分享 程序员 微信小程序 职场和发展