Rust中的Iterator和IntoIterator介绍及应用小结
作者:pilaf1990
Iterator即迭代器,它可以用于对数据结构进行迭代。被迭代的数据结构是可迭代的(iterable),所谓的可迭代就是这个数据结构有返回迭代器的方法,由于Rust的所有权机制,对一个数据结构的迭代器,有三种:
- 拿走数据结构的所有权的迭代器,在数据结构的方法上体现为
into_iter(self)
。在Rust中,into方法通常都是拿走所有权的,而且方法的入参self也表明了会拿走所有权,不会拿走所有权的是&self、&mut self这种入参。 - 不拿走数据结构的所有权,只读取数据结构内容的迭代器,在数据结构的方法上体现为
iter(&self)
。&self表明了只是只读借用。 - 不拿走数据结构的所有权,但是可以读写数据结构内容的迭代器,在数据结构的方法上体现为
iter_mut(&mut self)
。
每调用一次数据结构的迭代器方法就会返回一个迭代器(拿走数据结构所有权的迭代器方法只能调用一次),迭代器迭代完了就不能继续使用了。
迭代器在Rust中是一个trait:
pub trait Iterator { /// The type of the elements being iterated over. /// 迭代器迭代的元素类型 #[stable(feature = "rust1", since = "1.0.0")] type Item; /// Advances(向前移动) the iterator and returns the next value. /// /// Returns [`None`] when iteration is finished. Individual iterator /// implementations may choose to resume iteration, and so calling `next()` /// again may or may not eventually start returning [`Some(Item)`] again at some /// point. /// 迭代器取下一个元素的方法,如果没有下一个元素,会返回None /// 请注意next方法的入参是&mut self,也就是入参是可变的迭代器,为什么可变?因为迭代器内部通常都有 /// 记录迭代进度的变量,比如数组下标这种,随着迭代的进行,变量会自增,所以需要改变迭代器的状态,用可变借用 fn next(&mut self) -> Option<Self::Item>; /// 省略其它内容 }
你可以为你的数据结构实现Iterator trait,使得它可迭代。我们通常在for循环中使用迭代器。Rust标准库中的集合基本上都提供了返回迭代器的方法。比如我们最常用的Vec:
fn main() { // 下面是只读迭代器的使用 let students = vec!["张三".to_string(),"李四".to_string(),"韩老二".to_string()]; for student in &students{ println!("&students写法:{}",student); } // Vec的iter()方法返回的是只读迭代器 for student in students.iter(){ println!("iter()方法调用写法:{}",student); } let mut ugly_girls = vec!["韩老二".to_string(),"叶慧三".to_string()]; // for循环中的&mut ugly_girls写法等同于ugly_girls.iter_mut() for girl in &mut ugly_girls{ girl.push_str("--really "); } // iter_mut()方法返回的是读写迭代器,可以对被迭代的元素进行修改 for girl in ugly_girls.iter_mut(){ girl.push_str("ugly"); } println!("{:?}",ugly_girls); let ugly_boys = vec!["吴亦".to_string(),"肖障".to_string()]; // for ugly_boy in ugly_boys等同于for ugly_boy in ugly_boys.into_iter for ugly_boy in ugly_boys { println!("{}",ugly_boy); } // 这儿不能再访问ugly_boys了,因为它的所有权在for循环的时候就被转移到迭代器中了 }
执行上述的代码后输出:
&students写法:张三
&students写法:李四
&students写法:韩老二
iter()方法调用写法:张三
iter()方法调用写法:李四
iter()方法调用写法:韩老二
["韩老二--really ugly", "叶慧三--really ugly"]
吴亦
肖障
好了,让我们来自己搞一个数据结构,然后为它实现三个迭代器方法加深理解。
假设我们有一个struct:
#[derive(Debug)] pub struct SongList { // 歌单中歌曲列表 pub songs: Vec<String>, // 歌单创建时间 pub create_time: SystemTime, }
表示歌单的数据结构,如果我们想要在for循环中去迭代歌单内容,我们当然可以直接通过SongList的songs字段进行迭代,但是这个是利用了Vec为我们实现好的迭代器,如果我们不暴露SongList内部的结构,把SongList当成一个黑盒去遍历,我们需要为它实现几个迭代器方法,每个方法返回一个迭代器,从而可以在for循环中通过迭代器来迭代SongList。
我们需要三个自定义的struct:Iter、IterMut、IntoIter分别表示SongList的三种迭代器,然后需要SongList提供三个方法分别返回三种迭代器:
impl SongList { fn iter(&self) -> Iter { Iter::new(self) } fn iter_mut(&mut self) -> IterMut { IterMut::new(self) } fn into_iter(self)->IntoIter{ IntoIter::new(self) } }
实现只读迭代器
// 只读迭代器,会用到引用,所以struct要带生命周期范型,这儿遵循Rust习惯,用'a表示生命周期参数 pub struct Iter<'a> { // 迭代器本身不拥有被迭代的数据,而是引用被迭代的数据,所以需要定义引用,有人的地方就有江湖,同样有引用的地方就有生命周期'a pub song_list: &'a SongList, // 记录迭代进度的变量 pub index: usize, } impl<'a> Iter<'a> { // 传入&SongList引用,就可以创建迭代器Iter fn new(song_list: &'a SongList) -> Iter { Iter { song_list, index: 0, } } } // 为Iter实现Iterator trait,从而让它变成真正的迭代器 impl<'a> Iterator for Iter<'a> { type Item = &'a String; fn next(&mut self) -> Option<Self::Item> { if self.index < self.song_list.songs.len() { let result = Some(&self.song_list.songs[self.index]); self.index += 1; result } else { None } } } // 为了让我们可以在for循环中通过&song_list来替代song_list.iter(),需要为&SongList实现IntoIterator // 参考https://doc.rust-lang.org/std/iter/index.html中提到的内容 // If a collection type C provides iter(), it usually also implements IntoIterator for &C, // with an implementation that just calls iter(). Likewise, a collection C that provides iter_mut() // generally implements IntoIterator for &mut C by delegating to iter_mut(). This enables a convenient shorthand: // // let mut values = vec![41]; // for x in &mut values { // same as `values.iter_mut()` // *x += 1; // } // for x in &values { // same as `values.iter()` // assert_eq!(*x, 42); // } // assert_eq!(values.len(), 1); impl<'a> IntoIterator for &'a SongList { // 我们的SongList中被迭代的songs是Vec<String>,所以这儿迭代的Item就是&String type Item = &'a String; type IntoIter = Iter<'a>; fn into_iter(self) -> Self::IntoIter { self.iter() } } impl SongList { fn iter(&self) -> Iter { Iter::new(self) } } fn main() { let song_list = SongList { songs: vec![ "做个真的我".to_string(), "刀剑如梦".to_string(), "难念的经".to_string(), "任逍遥".to_string(), ], create_time: SystemTime::now(), }; for song in song_list.iter() { println!("{}", song); } for song in &song_list { println!("{}", song); } println!("song_list:{:#?}", song_list); }
实现可修改迭代器
// 读写迭代器 pub struct IterMut<'a> { // 要持有被迭代数据结构的可变应用 song_list: &'a mut SongList, index: usize, } impl<'a> IterMut<'a> { // 将&mut SongList变成IterMut的方法 fn new(song_list: &'a mut SongList) -> IterMut { IterMut { song_list, index: 0, } } } // 为IterMut实现Iterator trait,让它成为一个真正的迭代器 impl<'a> Iterator for IterMut<'a> { type Item = &'a mut String; fn next(& mut self) -> Option<Self::Item> { if self.index < self.song_list.songs.len() { let ptr = self.song_list.songs.as_mut_ptr(); let result = Some(unsafe{&mut *ptr.add(self.index)}); // 上面两行不能用下面这一行实现, // 否则会报错:error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements // 参考:https://stackoverflow.com/questions/62361624/lifetime-parameter-problem-in-custom-iterator-over-mutable-references // let result = Some(&mut self.song_list.songs[self.index]); self.index += 1; result } else { None } } } // 为了让我们可以在for循环中通过&mut song_list来替代song_list.iter_mut(),需要为&mut SongList实现IntoIterator impl<'a> IntoIterator for &'a mut SongList { type Item = &'a mut String; type IntoIter = IterMut<'a>; fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } impl SongList { fn iter_mut(&mut self) -> IterMut { IterMut::new(self) } } fn main() { let mut zhangsan_song_list = SongList { songs: vec!["笑脸盈盈".to_string(), "我是一只鱼".to_string()], create_time: SystemTime::now(), }; for song in &mut zhangsan_song_list{ song.push_str("-真的"); } for song in zhangsan_song_list.iter_mut() { song.push_str("-好好听啊"); } println!("zhagnsan_song_list:{:#?}", zhangsan_song_list); }
实现可以会拿走所有权的迭代器
// 定义一个会拿走所有权的迭代器struct pub struct IntoIter{ song_list:SongList // 不用index了,因为拿走所有权的话,从songs中拿走一个就少一个,不用再记录迭代进度了,迭代过的都被从Vec中移除了 } impl IntoIter { // 将SongList直接消耗掉,变成IntoIter,传入的song_list变量的所有权就这样被转移到了迭代器中 fn new(song_list:SongList)->Self{ IntoIter{ song_list } } } // 为IntoIter实现Iterator trait,让它成为一个真正的迭代器 impl Iterator for IntoIter { type Item = String; fn next(&mut self) -> Option<Self::Item> { // 从Vec中pop出的元素就没了,所以不需要我们额外定义index来记录迭代进度了 self.song_list.songs.pop() } } // 为了让我们可以在for循环中通过for item in song_list来替代for item in song_list.into_iter(),需要为SongList实现IntoIterator impl IntoIterator for SongList { type Item = String; type IntoIter = IntoIter; // 直接消耗掉SongList类型变量,将其所有权转移到迭代器中 fn into_iter(self) -> Self::IntoIter { IntoIter::new(self) } } impl SongList { fn into_iter(self)->IntoIter{ IntoIter::new(self) } } fn main() { let lisi_song_list = SongList{ songs:vec!["天涯".to_string(),"死不了".to_string()], create_time:SystemTime::now() }; //或者直接写成 for song in lisi_song_list{ for song in lisi_song_list.into_iter(){ println!("{}",song); } }
完整代码:
use std::iter::Iterator; use std::time::SystemTime; #[derive(Debug)] pub struct SongList { // 歌单中歌曲列表 pub songs: Vec<String>, // 歌单创建时间 pub create_time: SystemTime, } // 只读迭代器,会用到引用,所以struct要带生命周期范型,这儿遵循Rust习惯,用'a表示生命周期参数 pub struct Iter<'a> { // 迭代器本身不拥有被迭代的数据,而是引用被迭代的数据,所以需要定义引用,有人的地方就有江湖,同样有引用的地方就有生命周期'a pub song_list: &'a SongList, // 记录迭代进度的变量 pub index: usize, } impl<'a> Iter<'a> { // 传入&SongList引用,就可以创建迭代器Iter fn new(song_list: &'a SongList) -> Iter { Iter { song_list, index: 0, } } } // 为Iter实现Iterator trait,从而让它变成真正的迭代器 impl<'a> Iterator for Iter<'a> { type Item = &'a String; fn next(&mut self) -> Option<Self::Item> { if self.index < self.song_list.songs.len() { let result = Some(&self.song_list.songs[self.index]); self.index += 1; result } else { None } } } // 为了让我们可以在for循环中通过&song_list来替代song_list.iter(),需要为&SongList实现IntoIterator // 参考https://doc.rust-lang.org/std/iter/index.html中提到的内容 // If a collection type C provides iter(), it usually also implements IntoIterator for &C, // with an implementation that just calls iter(). Likewise, a collection C that provides iter_mut() // generally implements IntoIterator for &mut C by delegating to iter_mut(). This enables a convenient shorthand: // // let mut values = vec![41]; // for x in &mut values { // same as `values.iter_mut()` // *x += 1; // } // for x in &values { // same as `values.iter()` // assert_eq!(*x, 42); // } // assert_eq!(values.len(), 1); impl<'a> IntoIterator for &'a SongList { // 我们的SongList中被迭代的songs是Vec<String>,所以这儿迭代的Item就是&String type Item = &'a String; type IntoIter = Iter<'a>; fn into_iter(self) -> Self::IntoIter { self.iter() } } // 读写迭代器 pub struct IterMut<'a> { // 要持有被迭代数据结构的可变应用 song_list: &'a mut SongList, index: usize, } impl<'a> IterMut<'a> { // 将&mut SongList变成IterMut的方法 fn new(song_list: &'a mut SongList) -> IterMut { IterMut { song_list, index: 0, } } } // 为IterMut实现Iterator trait,让它成为一个真正的迭代器 impl<'a> Iterator for IterMut<'a> { type Item = &'a mut String; fn next(& mut self) -> Option<Self::Item> { if self.index < self.song_list.songs.len() { let ptr = self.song_list.songs.as_mut_ptr(); let result = Some(unsafe{&mut *ptr.add(self.index)}); // 上面两行不能用下面这一行实现, // 否则会报错:error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements // 参考:https://stackoverflow.com/questions/62361624/lifetime-parameter-problem-in-custom-iterator-over-mutable-references // let result = Some(&mut self.song_list.songs[self.index]); self.index += 1; result } else { None } } } // 为了让我们可以在for循环中通过&mut song_list来替代song_list.iter_mut(),需要为&mut SongList实现IntoIterator impl<'a> IntoIterator for &'a mut SongList { type Item = &'a mut String; type IntoIter = IterMut<'a>; fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } // 定义一个会拿走所有权的迭代器struct pub struct IntoIter{ song_list:SongList // 不用index了,因为拿走所有权的话,从songs中拿走一个就少一个,不用再记录迭代进度了,迭代过的都被从Vec中移除了 } impl IntoIter { // 将SongList直接消耗掉,变成IntoIter,传入的song_list变量的所有权就这样被转移到了迭代器中 fn new(song_list:SongList)->Self{ IntoIter{ song_list } } } // 为IntoIter实现Iterator trait,让它成为一个真正的迭代器 impl Iterator for IntoIter { type Item = String; fn next(&mut self) -> Option<Self::Item> { // 从Vec中pop出的元素就没了,所以不需要我们额外定义index来记录迭代进度了 self.song_list.songs.pop() } } // 为了让我们可以在for循环中通过for item in song_list来替代for item in song_list.into_iter(),需要为SongList实现IntoIterator impl IntoIterator for SongList { type Item = String; type IntoIter = IntoIter; // 直接消耗掉SongList类型变量,将其所有权转移到迭代器中 fn into_iter(self) -> Self::IntoIter { IntoIter::new(self) } } impl SongList { fn iter(&self) -> Iter { Iter::new(self) } fn iter_mut(&mut self) -> IterMut { IterMut::new(self) } fn into_iter(self)->IntoIter{ IntoIter::new(self) } } fn main() { let song_list = SongList { songs: vec![ "做个真的我".to_string(), "刀剑如梦".to_string(), "难念的经".to_string(), "任逍遥".to_string(), ], create_time: SystemTime::now(), }; for song in song_list.iter() { println!("{}", song); } for song in &song_list { println!("{}", song); } println!("song_list:{:#?}", song_list); let mut zhangsan_song_list = SongList { songs: vec!["笑脸盈盈".to_string(), "我是一只鱼".to_string()], create_time: SystemTime::now(), }; for song in &mut zhangsan_song_list{ song.push_str("-真的"); } for song in zhangsan_song_list.iter_mut() { song.push_str("-好好听啊"); } println!("zhagnsan_song_list:{:#?}", zhangsan_song_list); let lisi_song_list = SongList{ songs:vec!["天涯".to_string(),"死不了".to_string()], create_time:SystemTime::now() }; //或者直接写成 for song in lisi_song_list{ for song in lisi_song_list.into_iter(){ println!("{}",song); } }
运行的控制台输出:
做个真的我
刀剑如梦
难念的经
任逍遥
做个真的我
刀剑如梦
难念的经
任逍遥
song_list:SongList {
songs: [
"做个真的我",
"刀剑如梦",
"难念的经",
"任逍遥",
],
create_time: SystemTime {
tv_sec: 1690101338,
tv_nsec: 120053000,
},
}
zhagnsan_song_list:SongList {
songs: [
"笑脸盈盈-真的-好好听啊",
"我是一只鱼-真的-好好听啊",
],
create_time: SystemTime {
tv_sec: 1690101338,
tv_nsec: 120146000,
},
}
死不了
天涯
到此这篇关于Rust中的Iterator和IntoIterator介绍及应用的文章就介绍到这了,更多相关Rust中的Iterator和IntoIterator内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!