about
Bits_Iter Rust
08/01/2024
0
Bits: Rust Iteration
iterators, for-in loops, iterable custom types, iterator methods
Synopsis:
This bit demonstrates uses of Rust iterators to walk through enumerable collections. The purpose is to quickly acquire some familiarity with Rust iteration.-
Rust iterable containers support two functions that return iterators:
iter() for non-modifying traversals anditer_mut() for modifying traversals. -
Iterator
iter() supports the functionfn next(&self) -> Option<Self::Item> . -
Iterator
iter_mut() supportsfn next(&mut self) -> Option<Self::Item> . -
While walking the collection
next() returnsSome(item) . When the end of its collection is reached,next() returnsNone . -
Collections may also support the IntoIterator trait with function
into_iter(self) which consumes the collection and returns an iterator over its elements. -
Rust Strings are a special case. Unlike
vec andslice &T[] , string items are not all the same size. strings hold utf-8 characters which vary in size from 1 to 4 bytes. So strings support two iterators:chars(&self) , for moving from character to character, andbytes(&self) for stepping through each of the string's bytes. - Rust for-loops are simplifying syntax wrappers around iterators.
Demo Notes
1.0 Iterators
Creates an instance of an iterator provided by C
and consumes c. Elements of |
|
Returns an iterator without consuming Elements of |
|
Returns an iterator without consuming Elements of |
2.0 Source Code
2.1 Loop Iteration over Vec<T>
/*---------------------------------------------------------
Demo Vec iteration with loop construct
- illustrates how iteration works, using most basic
syntax operating on Vec<T> instances.
*/
fn demo_loop_iteration() {
show_label("basic loop iteration with Vec", 35);
/* 1. v.iter(), iterates without consuming v */
let v = vec![1, 2, 3, 2, 1];
show_op("Vec iteration using loop and iter()");
let mut itr = v.iter();
loop {
match itr.next() {
Some(item) => print!("{item} "),
None => break,
}
}
// next statement is valid since v not consumed
println!();
println!("{v:?}");
/* 2. v.iter_mut() iterates and mutates without consuming v */
let mut v = vec![1, 2, 3, 2, 1];
let mut mitr = v.iter_mut();
show_op("mutable vec iteration with loop and mut_iter()");
loop {
match mitr.next() {
Some(item) => { *item += 1; print!("{item} "); }
None => break,
}
}
println!();
println!("{v:?}");
/* 3. v.into_iter() consumes v while converting to iterator */
let mut itr = v.into_iter();
show_op("generate iterator with v.into_iter()");
loop {
match itr.next() {
Some(item) => print!("{item} "),
None => break,
}
}
// into_iter() consumes v so the next statement is invalid
// println!("{v:?}"); // v was moved
println!();
}
-----------------------------------
basic loop iteration with Vec
-----------------------------------
--- Vec iteration using loop and iter() ---
1 2 3 2 1
[1, 2, 3, 2, 1]
--- mutable vec iteration with loop and mut_iter() ---
2 3 4 3 2
[2, 3, 4, 3, 2]
--- generate iterator with v.into_iter() ---
2 3 4 3 2
2.2 Iteration with for-in Loops
/* do something with item */ } |
|
/* do something with item */ } |
Since reference |
/* do something that mutates item */ } |
Since |
/*---------------------------------------------------------
Demo iteration with for-in loop construct
- illustrates how for-in works, using idiomatic
syntax operating on Vec<T> instances.
*/
fn demo_for_iteration() {
show_label("basic for-in loop iteration using Vec", 45);
/* 1. v.iter(), iterates without consuming v */
let v = vec![1, 2, 3, 2, 1];
show_op("Vec iteration using v.iter()");
for item in v.iter() {
print!("{item} ");
}
// next statement is valid since v not consumed
println!();
println!("{v:?}");
/* 2. v.iter_mut() iterates and mutates without consuming v */
let mut v = vec![1, 2, 3, 2, 1];
show_op("mutable vec iteration using v.iter_mut()");
println!("original: {v:?}");
for item in v.iter_mut() {
*item += 1;
print!("{item:?} ");
}
println!();
println!("after iter: {v:?}");
/* 3. v.into_iter() consumes v while converting to iterator */
// let mut itr = v.into_iter();
show_op("for-in uses v.into_iter()");
for item in v.into_iter() {
print!("{item:?} ");
}
// into_iter() consumes v so the next statement is invalid
// println!("{v:?}"); // v was moved
println!();
/*
4. iteration with for-in consumes v
- same as 3. except that into_iter() is used implicitly
- used in preference to 3
*/
let v = vec![1, 2, 3, 4, 5];
show_op("for-in uses v => into_iter()");
for item in v { // implicitly uses into_iter()
print!("{item:?} ");
}
// into_iter() consumes v so the next statement is invalid
// println!("{v:?}"); // v was moved
println!();
/*
5. iteration over elements of v using &v
- uses internal call to into_iter() implemented with
Vec::iter() so v not moved
*/
let v = vec![1, 2, -1, -2, 0];
show_op("for-in uses &v => iter()");
for item in &v {
print!("{item:?} ");
}
println!();
println!("{v:?}"); // v was not moved
/*
6. mutating iteration over elements of v using &mut v
- generates into_iter() implemented with internal
call to iter_mut(), so does not move v
*/
let mut v = vec![1, 2, -1, -2, 0];
show_op("for-in uses &mut v => iter_mut()");
println!("original: {v:?}");
for item in &mut v {
*item += 1;
print!("{item:?} ");
}
println!();
println!("modified: {v:?}"); // v was not moved
/*-------------------------------------------------------
Iteration forms 4, 5, and 6 are the preferred useage.
Forms 1, 2, and 3 show how for-in loops work.
-------------------------------------------------------*/
}
---------------------------------------------
basic for-in loop iteration using Vec
---------------------------------------------
--- Vec iteration using v.iter() ---
1 2 3 2 1
[1, 2, 3, 2, 1]
--- mutable vec iteration using v.iter_mut() ---
original: [1, 2, 3, 2, 1]
2 3 4 3 2
after iter: [2, 3, 4, 3, 2]
--- for-in uses v.into_iter() ---
2 3 4 3 2
--- for-in uses v => into_iter() ---
1 2 3 4 5
--- for-in uses &v => iter() ---
1 2 -1 -2 0
[1, 2, -1, -2, 0]
--- for-in uses &mut v => iter_mut() ---
original: [1, 2, -1, -2, 0]
2 3 0 -1 1
modified: [2, 3, 0, -1, 1]
2.3 Generate CSL for Several Different Types
/*---------------------------------------------------------
Demonstrate iter() by displaying a comma seperated
list (csl) of items in several common collections.
- three different strategies used for making
display comma-seperated
- syntax used in this demo
- iter().next() -> Option<Self::Item>
- enum Option<T> { Some(T), None, }
*/
fn demo_iter() {
show_label("demo_iter()", 20);
/*--------------------------------------------------
iterate over array
csl strategy #1 extracts first item before iterating
--------------------------------------------------*/
show_op("array iter using loop");
let ar = [1, 2, 3, 4];
let mut iter = ar.iter(); // extract first item
if let Some(item) = iter.next() {
print!("{item}");
}
loop {
let item = iter.next();
match item { //display remaining items
Some(item) => print!(", {}", item),
None => break
}
}
println!();
// ar not consumed by ar.iter(), above
// so statement below is valid:
println!("using println!:\n{:?}", ar);
/*--------------------------------------------------
iterate over Vec
csl strategy #2 uses first flag
--------------------------------------------------*/
/*--- functionaly equivalent to loop, above ---*/
show_op("Vec iter using for-in");
let v = vec![1, 2, 3, 4];
let mut first = true; // set first flag
for item in &v {
if first {
print!("{item}");
first = false; // reset first flag
}
else {
print!(", {item}");
}
}
println!();
// statement below is valid, v not consumed since
// for-in used reference &v
println!("using println!:\n{:?}", v);
/*--------------------------------------------------
iterate over HashMap
csl strategy #3 uses enumerate()
--------------------------------------------------*/
show_op("HashMap iter using for-in");
let mut hm = HashMap::<&str, i32>::new();
hm.insert("zero", 0);
hm.insert("one", 1);
hm.insert("two", 2);
hm.insert("three", 3);
/*-------------------------------------------------------
enumerate is an iterator adapter that returns another
iterator yielding (count, value) where value is
yielded by iter
*/
for (count, item) in hm.iter().enumerate() {
if count == 0 {
print!("{item:?}");
}
else {
print!(", {item:?}");
}
}
println!();
println!("using println!:\n{:?}", hm);
/*--------------------------------------------------
iterate over Point<T, N>,
csl strategy same as above
--------------------------------------------------*/
show_op("Point iter using for-in");
let mut p = Point::<f64, 5>::new();
p.init(&vec![1.0, 1.5, 2.0, 1.5, 1.0]);
for item in p.iter().enumerate() {
if item.0 == 0 { // count == zero
print!("{:?}", item.1);
}
else { // count > 0
print!(", {:?}", item.1);
}
}
println!();
print!("using println!:\n{p:?}"); // p not moved
println!("\n");
/*--------------------------------------------------
Use formatting function that accepts any type
implementing IntoIterator trait.
- function defined below
--------------------------------------------------*/
show_op("using show_csl(&ar) for array");
show_csl(&ar); // ar not consumed
show_op("using show_csl(&v) for Vector");
show_csl(&v); // v not consumed
show_op("using show_csl(&hm) for HashMap");
show_csl(&hm); // hm not consumed
show_op("using show_csl(&p) for Point");
show_csl(&p); // p not consumed
println!();
show_op("using show_csl(ar) for array - copies ar");
show_csl(ar); // ar is not consumed as it is a copy type
show_op("using show_csl(v) for Vector - moves v");
show_csl(v); // v is consumed
show_op("using show_csl(hm) for HashMap - moves hm");
show_csl(hm); // hm is consumed
show_op("using show_csl(p) for Point - moves p");
show_csl(p); // p is consumed
}
/* generalize csl strategy #3 */
fn show_csl<C>(c:C) // consumes c
where C: IntoIterator, C::Item: Debug
{
let iter = c.into_iter();
for (count, val) in iter.enumerate() {
if count == 0 {
print!("{:?}", val);
}
else {
print!(", {:?}", val);
}
}
println!();
}
--------------------
demo_iter()
--------------------
--- array iter using loop ---
1, 2, 3, 4
using println!:
[1, 2, 3, 4]
--- Vec iter using for-in ---
1, 2, 3, 4
using println!:
[1, 2, 3, 4]
--- HashMap iter using for-in ---
("one", 1), ("three", 3), ("zero", 0), ("two", 2)
using println!:
{"one": 1, "three": 3, "zero": 0, "two": 2}
--- Point iter using for-in ---
1.0, 1.5, 2.0, 1.5, 1.0
using println!:
Point { items: [1.0, 1.5, 2.0, 1.5, 1.0] }
--- using show_csl(&ar) for array ---
1, 2, 3, 4
--- using show_csl(&v) for Vector ---
1, 2, 3, 4
--- using show_csl(&hm) for HashMap ---
("one", 1), ("three", 3), ("zero", 0), ("two", 2)
--- using show_csl(&p) for Point ---
1.0, 1.5, 2.0, 1.5, 1.0
--- using show_csl(ar) for array - copies ar ---
1, 2, 3, 4
--- using show_csl(v) for Vector - moves v ---
1, 2, 3, 4
--- using show_csl(hm) for HashMap - moves hm ---
("one", 1), ("three", 3), ("zero", 0), ("two", 2)
--- using show_csl(p) for Point - moves p ---
1.0, 1.5, 2.0, 1.5, 1.0
2.4 Iteration over custom Point<T, N> Coordinates
/*-- Point<T, N> --------------------------------
Point<T, N> declares a Point type holding a
Vec<T> of coordinate values.
It implements:
- new(n) constructor
- iter() returns iterator over items
- iter_mut() mutates while iterating
- trait IntoIterator for Point<T, N>
- trait IntoIterator for &Point<T, N>
- trati IntoIterator for &mut Point<T, N>
- immutable and mutable indexing
Note:
---------------------------------------------
This is a nice example of building a custom
collection type. It implements methods and
traits necessary to make a collection behave
like standard library collections.
---------------------------------------------
*/
use std::fmt::*;
#[derive(Debug, Clone)]
pub struct Point<T, const N: usize>
where T:Debug + Default + Clone
{
pub items: Vec<T>
}
impl<T, const N:usize> Point<T, N>
where T:Debug + Default + Clone
{
/*-- constructor --*/
pub fn new() -> Point<T, N> {
Point::<T, N> {
items: vec![T::default(); N],
}
}
pub fn init(&mut self, v:&Vec<T>) {
for i in 0..v.len() {
self.items[i] = v[i].clone();
}
for i in v.len()..N {
self.items[i] = T::default();
}
}
/*-- non-destructive non-mutating iterator */
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.items.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.items.iter_mut()
}
}
/*-- implements const indexer -----------------*/
impl<T, const N:usize, Idx> std::ops::Index<Idx> for Point<T, N>
where
T:Debug + Default + Clone,
Idx: std::slice::SliceIndex<[T]>
{
type Output = Idx::Output;
fn index(&self, index:Idx) -> &Self::Output {
&self.items[index]
}
}
/*-- implements mutable indexer ---------------*/
impl<T, const N:usize, Idx> std::ops::IndexMut<Idx> for Point<T, N>
where
T:Debug + Default + Clone,
Idx: std::slice::SliceIndex<[T]>
{
fn index_mut(&mut self, index:Idx) -> &mut Self::Output {
&mut self.items[index]
}
}
/*-- IntoIterator trait for PointN<T> ---------*/
impl<T, const N:usize> IntoIterator for Point<T, N>
where T:Debug + Default + Clone
{
type Item = T;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.items.into_iter()
}
}
/*-- IntoIterator trait for &Point<T, N> -------------
- Supports interating elements of Point
- Point instance is not moved because we use
Vec::iter() internally
- a is a required lifetime annotation
*/
use core::slice::Iter;
impl<'a, T, const N:usize> IntoIterator for &'a Point<T, N>
where T:Debug + Default + Clone
{
type Item = &'a T;
type IntoIter = Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.items.iter()
}
}
/*-- IntoIterator trait for &mut Point<T, N> ---------
- Supports mutating elements of Point while
iterating. No clone used here.
- Point instance is not moved because we use
Vec::iter_mut() internally
- a is a required lifetime annotation
*/
use core::slice::IterMut;
impl<'a, T, const N:usize> IntoIterator for &'a mut Point<T, N>
where T:Debug + Default + Clone
{
type Item = &'a mut T;
type IntoIter = IterMut<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.items.iter_mut()
}
}
2.5 Point<T, N> Demonstration
/*---------------------------------------------------------
Demo iteration over coordinate values in Point<T,N>
- illustrates how to implement iteration for custom
types, using definitions in points_iter.rs module.
*/
fn demo_point_iteration() {
show_label("demo point iteration", 30);
/* uses Point<T,N>::IntoIterator => into_iter() => move */
let mut p = Point::<i32, 5>::new();
p.init(&vec![3, 2, 1, 0, -1]);
show_op("for-in uses p, generating iter from p.into_iter()");
for item in p {
print!("{item:?} ");
}
// into_iter() consumes v so the next statement is invalid
// println!("{p:?}"); // v was moved
println!();
/* uses &Point<T,N>::IntoIterator => iter() => no move */
let mut p = Point::<i32, 5>::new();
p.init(&vec![3, 2, 1, 0, -1]);
show_op("for-in uses &p, generating iter from &p.iter()");
println!("original: {p:?}");
for item in &p {
print!("{item:?} ");
}
println!();
println!("after iter: {p:?}"); // v was not moved
/* uses &mut Point<T,N>::IntoIterator => iter_mut() => no move */
let mut p = Point::<i32, 5>::new();
p.init(&vec![-3, -2, -1, 0, 1]);
show_op("for-in uses &mut p, generating iter from &mut p.iter_mut()");
println!("original: {:?}", p);
for item in &mut p {
*item += 1;
print!("{item:?} ");
}
println!();
println!("modified: {p:?}"); // v was not moved
}
------------------------------
demo point iteration
------------------------------
--- for-in uses p, generating iter from p.into_iter() ---
3 2 1 0 -1
--- for-in uses &p, generating iter from &p.iter() ---
original: Point { items: [3, 2, 1, 0, -1] }
3 2 1 0 -1
after iter: Point { items: [3, 2, 1, 0, -1] }
--- for-in uses &mut p, generating iter from &mut p.iter_mut() ---
original: Point { items: [-3, -2, -1, 0, 1] }
-2 -1 0 1 2
modified: Point { items: [-2, -1, 0, 1, 2] }
2.6 Iterator Methods
Selection of iterator methods from std::Iter | |
---|---|
all<F>
(&mut self, f: F) -> bool where Self: Sized, F: FnMut(Self::Item) -> bool |
Tests if every item of the iterator matches a predicate. |
any<F>
(&mut self, f: F) -> bool where Self: Sized, F: FnMut(Self::Item) -> bool |
Tests if any item of the iterator matches a predicate. |
cloned<'a T>
(self) -> Cloned<Self> where T: 'a + Clone', Self: Sized + Iterator<Item = &'a T>" |
Consumes an iterator to create an iterator that clones all of its elements. |
collect<B>
(self) -> B where B: FromIterator<Self::Item>, Self: Sized |
Transforms an iterator into a collection, consuming the iterator. |
enumerate
(self) -> Enumerate<Self> where Self: Sized |
Consumes iterator to create an iterator which gives current iteration count and the next value |
filter<P>
(self, predicate: P) -> Filter<Self, P> where P: FnMut(&Self::Item) -> bool, Self: Sized |
Consumes iterator to create an iterator which takes a closure to determine if an item should be yielded. |
for_each<F>
(self, f: F) where F: FnMut(Self::Item), Self: Sized |
Calls a closure f on each element of an iterator to make in-place changes of items |
map<B, F>
(self, f: F) -> Map<Self, F> where F: FnMut(Self::Item) -> B, Self: Sized |
Creates an iterator that calls a closure f on each item |
/*---------------------------------------------------------
Demonstrate iterator methods
*/
fn demo_methods() {
show_label("iterator methods", 25);
show_op("original vector");
let mut v = vec![1, 2, 3, 2, 1];
println!("{v:?}");
show_op("modified using for_each()");
/* inplace modification of elements of v */
v.iter_mut().for_each(|item| *item *= *item);
println!("{v:?}");
show_op("collect squared items from vec into array");
let sq:[i32; 5] =
/* return iterator over squared items from v */
v.iter().map(|&item| item * item)
/* collect invokes iterator to load modified elements into sq */
.collect::<Vec<i32>>().try_into()
/* display message if collection panics, e.g., fails and terminates */
.expect("incorrect length");
println!("{sq:?}");
show_op("filter out elements larger than 20");
let filtered: Vec<i32> =
/* create iterator over filtered elements */
sq.iter().filter(|&&item| item <= 20)
/* copy filtered element and collect into Vec */
.cloned().collect();
println!("{filtered:?}");
}
-------------------------
iterator methods
-------------------------
--- original vector ---
[1, 2, 3, 2, 1]
--- modified using for_each() ---
[1, 4, 9, 4, 1]
--- collect squared items from vec into array ---
[1, 16, 81, 16, 1]
--- filter out elements larger than 20 ---
[1, 16, 16, 1]
2.7 Program Structure
Code Structure
/*-----------------------------------------------
Bits::rust_iter::main.rs
- demonstrates iteration over collections with
Rust iterators
- Most collections implement the Rust trait
IntoIterator which consumes the collection
to generate an iterator.
- Many also supply functions iter() and mut_iter()
which return iterators without consuming originial
collection.
- Demonstrates iteration over arrays, slices,
Vecs, VecDeques, and custom Point<T, N> type.
-----------------------------------------------*/
#![allow(dead_code)]
#![allow(unused_variables)]
/*-----------------------------------------------
- Module analysis_iter provides functions
for type analysis and display.
- Module points_iter defines type Point<T, N>,
a point in N-dimensional hyperspace.
*/
use std::collections::*;
mod analysis_iter;
use analysis_iter::*;
mod points_iter;
use points_iter::*;
use std::fmt::*;
use std::cmp::*;
fn demo_loop_iteration() {
/* code elided */
}
fn demo_for_iteration() {
/* code elided */
}
fn demo_iter() {
/* code elided */
}
fn demo_point_iteration() {
/* code elided */
}
/*-- Begin demonstrations ---------------------*/
fn main() {
analysis_iter::show_label("Demonstrate Rust Iteration",30);
demo_loop_iteration();
demo_for_iteration();
demo_iter();
demo_point_iteration();
println!("\nThat's all folks!\n");
}
Program Structure:
This program illustrates how iterators are created and used. It also shows how custom types can declare and define their own iterators. This demonstration is partitioned into three modules: - main.rs (this file) a sequence of demonstration functions, each focused on one type of syntax and the main function that controls processing - points_iter.rs defines custome typePoint<T, N> - analysis_iter.rs defines functions for type analysis and display Each of the functions invoked here are illustrated using blocks with left and right panels, shown above this block. Code is shown in a left panel and output in a right panel, seperated by a splitter-bar enabling views of content that may be hidden in default display.
3.0 Build
C:\github\JimFawcett\Bits\Rust\rust_iter
> cargo run
Compiling rust_iter v0.1.0 (C:\github\JimFawcett\Bits\Rust\rust_iter)
Finished dev [unoptimized + debuginfo] target(s) in 0.50s
C:\github\JimFawcett\Bits\Rust\rust_iter
>
4.0 VS Code View
5.0 References
Reference | Description |
---|---|
RustBite_Iterators | RustBite on Iterators and Adapters |
Rust Story | E-book with seven chapters covering most of intermediate Rust |
Rust Bites | Relatively short feature discussions |
std::Iter | Library documentation for all of the standard methods. These are provided by any type that implements the Iterator trait. |
Point<T, N> Definition: