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: BufRead> Iterator for Lines<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> IntoIterator for Vec<T>
繼續查找IntoIterator就會發現這個東東是對Iterator的包裝。
pub trait IntoIterator where Self::IntoIter::Item == Self::Item { type Item; type IntoIter: Iterator; fn into_iter(self) -> Self::IntoIter; }
for循環中通過intoIterator返回一個迭代器,對Vec的循環等價於
let result = match IntoIterator::into_iter(&v) {
mut iter => loop {
match iter.next() {
Some(x) => { print!("{}", x); }
None => break,
}
},
};
這樣保證了即使外層嵌套了循環,內層執行時都是重新生成一個迭代器,從而避免了迭代器用之即棄的問題。
以上個人觀點,如果有錯誤請不吝指教。歡迎討論,共同學習。
參考資料:
https://hardocs.com/d/rustprimer/iterator/iterator.html
https://www.rust-lang.org/learn