about
Bits_Iter Rust
06/03/2023
Bits_Iter Rust
code, output, and build for Rust on Windows, macOS, and Linux
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 CodeSnaps
Source Code - main.rs
/*----------------------------------------------- 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. - Demonstrates iteration over arrays, slices, Vecs, VecDeques, and custom PointN<T> type. -----------------------------------------------*/ #![allow(dead_code)] #![allow(unused_variables)] use std::collections::*; mod analysis_iter; // use analysis_iter::*; mod points_iter; use points_iter::*; /*--------------------------------------------------------- Use of Rust iterators is encapsulated in a sequence of functions defined below and used in main. This file builds collections and applies the various functions to illustrate how iterators are used. It starts with a few quick iteration examples before applying the analysis functions. ---------------------------------------------------------*/ /* - vec_indexer<T:Debug>(v:&Vec<T>) - slice_indexer<T:Debug>(s:&[T]) - sub_range_indexer<T:Debug>( s:&[T], mut lower:usize, mut upper:usize ) - slice_looper<T:Debug>(s:&[T]) - collection_looper<C: Debug, I: Debug>(c:&C) where C: IntoIterator<Item = I> + Clone - for_looper<C: Debug, I: Debug>(c:&C) where C: IntoIterator<Item = I> + Clone - ranger<T>(iter: &mut T) where T: Iterator, T::Item: Debug */ use std::fmt::*; use std::cmp::*; /*----------------------------------------------- vec_indexer<T: Debug>(v:&Vec<T>) ------------------------------------------------- Simplest case - displays Vector with generic Item type. - uses naive indexing - works only for Vec's */ pub fn vec_indexer<T: Debug>(v:&Vec<T>) { let mut i = 0; while i < v.len() { print!("{:?} ", v[i]); i += 1; } println!(); } /*----------------------------------------------- slice_indexer<T:Debug>(s:&[T]) ------------------------------------------------- Illustrates indexing in slices - Not idiomatic, but safe and correct. - Works with any collection with contiguous fixed size elements. - Won't work with String or str inputs; they are collections of utf8 chars which vary in size from 1 to 4 bytes. - It is rare for idomatic Rust code to use indexing. - &[T] is slice type - Demo's in main show how to use for various types. */ #[allow(clippy::needless_range_loop)] pub fn slice_indexer<T:Debug>(s:&[T]) { let max = s.len(); /* 0..max is range iterator */ for i in 0..max { print!("{:?} ", s[i]); } println!(); /*--------------------------- clippy prefers no indexing: for item in s.iter().take(max) { print!("{item} "); } */ } /*----------------------------------------------- sub_range_indexer<T:Debug>( s:&[T], mut lower:usize, mut upper:usize ) Iterates over a sub-range of the slice s - works with any collection with contiguous fixed size elements. */ #[allow(clippy::needless_range_loop)] pub fn sub_range_indexer<T:Debug>( s:&[T], mut lower:usize, mut upper:usize ) { lower = max(0, lower); upper = min(s.len(), upper); if lower <= upper { for i in lower..upper { print!("{:?} ", s[i]); } } println!(); } /*----------------------------------------------- slice_looper<T:Debug>(s:&[T]) ------------------------------------------------- Iterates over slice s without indexing - Works with any collection with contiguous fixed size elements, e.g., array, Vector, PointN, ... - Uses slice iterator. */ pub fn slice_looper<T:Debug>(s:&[T]) { let mut iter = s.iter(); loop { let item = iter.next(); match item { Some(val) => print!("{val:?} "), None => break } } println!(); } /*----------------------------------------------- collection_looper<C:Debug, I:Debug>(c:&C) ------------------------------------------------- - prints comma separated list of Collection<I>'s items. - where clause requires C to implement IntoIterator trait. - C is type of collection, I is type of C's items - Accepts any collection type that implements IntoIterator trait, e.g., array, slice, Vector, ... - collection_looper can't accept String or &str because String does not implement IntoIterator - That's because String provides two iterators, chars() to iterate multibyte chars and bytes() to iterate over bytes. - Not very efficient - uses three order N operations, clone, collect, and loop. https://stackoverflow.com/questions/49962611/why-does-str-not-implement-intoiterator */ pub fn collection_looper<C: Debug, I: Debug>(c:&C) where C: IntoIterator<Item = I> + Clone { let cc = c.clone(); let iter = cc.into_iter(); /* convert c into Vec to get len() method */ let v:Vec<_> = iter.collect(); let mut iter = v.iter(); // shadowing let mut count = 0; loop { let item = iter.next(); match item { Some(val) => print!("{val:?}"), None => { println!(); break; } } if count < v.len() - 1 { print!(", "); } count += 1; } } /*----------------------------------------------- for_looper<C: Debug, I: Debug>(c:&C) ------------------------------------------------- - prints comma separated list of Collection<I>'s items. - similar to collection_looper but erases last comma so no need for collection or clone - uses idiomatic forloop with no indexing */ pub fn for_looper<C: Debug, I: Debug>(c:&C) where C: IntoIterator<Item = I> + Clone { /* build string of comma separated values */ let mut accum = String::new(); let cc = c.clone(); for item in cc { // converts cc into iterator accum += &format!("{item:?}, "); } /* remove last comma */ let opt = find_last_utf8(&accum, ','); if let Some(index) = opt { accum.truncate(index); } println!("{accum}"); } /*-- find last char in str --*/ pub fn find_last_utf8(s:&str, chr: char) -> Option<usize> { s.chars().rev().position(|c| c == chr) .map(|rev_pos| s.chars().count() - rev_pos -1) } /*----------------------------------------------- ranger<T>(iter: &mut T) ------------------------------------------------- - Displays contents of iterator, often passed in as range. - another idiomatic iteration. */ pub fn ranger<T>(iter: &mut T) where T: Iterator, T::Item: Debug { for item in iter { print!("{item:?} ") } println!(); } /*----------------------------------------------- demo_adapters<C, I>(c: C, i: I) -> Vec<I> ------------------------------------------------- - iterates over collection C, removes non-positive items, adds second argument i and collects into vector. ------------------------------------------------- - adapters accept an iterator and return a revised iterator, as discussed below. - adapter filter builds iterator over elements that satisfy a predicate defined by closure - map builds iterator that modifies elements according to a closure. - Adapter collect runs iterator and collects into Vec<I>. */ pub fn demo_adapters<C, I>(c: C, i: I) -> Vec<I> where C: IntoIterator<Item = I> + Debug + Clone, I: std::ops::Add<Output = I> + std::ops::Mul<Output = I> + PartialOrd + PartialEq + Debug + Default + Copy, { let def = I::default(); // expect value is zero c.into_iter() .filter(|item| item > &def) .map(|item| item + i) .collect() } /*-- Begin demonstrations ---------------------*/ fn main() { analysis_iter::show_label("Demonstrate Rust Iteration",30); let s = &mut [1usize, 2, 3, 4, 3, 2, 1]; println!("slice s = {s:?}"); println!("s[2] = {:?}", s[2usize]); /*-- PointN<T>.into_iter() -----------------*/ let mut p = PointN::<i32>::new(5); p[1] = 1; p[3] = -1; println!("\n{p:?}"); println!("using PointN<i32>.iter"); for item in p.iter() { print!("{item:?} "); } println!("\nusing PointN<i32>.into_iter"); let iter = p.clone().into_iter(); // consumes clone analysis_iter::show_op("displaying iter type"); analysis_iter::show_type(&iter, "iter"); for item in iter { print!("{item} "); } println!(); println!("using PointN<i32>.into_iter iter() with auto deref"); let pc = p.clone(); for item in pc { // auto deref of pc into pc.iter() print!("{item} " ) // consumes pc } println!(); println!("using PointN<i32>.iter()"); for item in p.iter() { // does not consume p print!("{item} " ) } println!(); println!("using PointN<i32>.iter_mut()"); for item in p.iter_mut() { // does not consume p *item *= 2; print!("{item} " ) } println!("\n{p:?}"); /*-- vec_indexer -------------------------------*/ println!("\nvec_indexer displays Vec<T>"); let v = vec![1, 6, 2, 5, 3, 4]; vec_indexer(&v); // can only display vecs println!(); /*-- slice_indexer -----------------------*/ println!("slice_indexer displays slice"); slice_indexer(s); println!("slice_indexer displays vector"); let v = vec![5, 4, 3, 2, 1, 0]; slice_indexer(&v); println!("slice_indexer displays string bytes"); let str1:&str = "a string"; slice_indexer(str1.as_bytes()); println!(); /*-- sub_range_indexer --------------------*/ println!("sub_range_indexer displays slice"); sub_range_indexer(s, 2, 5); println!(); /*-- slice_looper ------------------------*/ println!("slice_looper displays slice"); slice_looper(s); println!("slice_looper displays vector"); slice_looper(&v); println!("slice_looper displays PointN"); let mut point = PointN::<i32>::new(5); point[1] = 2; point[3] = -3; let ps = &point[0..]; // take slice slice_looper(ps); println!(); /*-- collection_looper -------------------------------*/ println!("collection_looper displays slice:"); collection_looper(s); println!("collection_looper displays array"); let a = [1, 2, 3]; collection_looper(&a); println!("collection_looper displays VecDeque"); let vecdeq = VecDeque::from([4, 3, 2, 0, -1]); collection_looper(&vecdeq); println!("collection_looper displays PointN"); let pc = point.clone(); collection_looper(&pc); println!("{pc:?}"); println!(); /*-- for_looper ---------------------------*/ println!("for_looper displays slice:"); for_looper(s); println!("for_looper displays vector:"); let vec = s.to_vec(); for_looper(&vec); println!("for_looper displays VecDeque"); for_looper(&vecdeq); println!("for_looper displays PointN<T>"); let pc = point.clone(); for_looper(&pc); println!(); /*------------------------------------------- for_looper can't accept String or &str because they do not implement IntoIterator https://stackoverflow.com/questions/49962611/why-does-str-not-implement-intoiterator */ /*-- ranger -------------------------------*/ println!("ranger displays string:"); let str = "a literal string".to_string(); ranger(&mut str.chars()); println!("ranger displays values in range"); ranger(&mut (0..10)); println!("ranger accepts Vector iterator"); ranger(&mut vec.iter()); println!("ranger accepts VecDeque iterator"); ranger(&mut vecdeq.iter()); println!("ranger accepts PointN<T> iterator"); ranger(&mut point.iter()); println!(); /*-- demo_adapters ------------------------*/ println!("demo_adapters<T, i32>(coll, 2) accepts array:"); let a = [1, -1, 0, 2, 3, 4]; println!("{:?} ", &a); let vo = demo_adapters(a, 2); println!("{:?} ", &vo); println!("demo_adapters<T, f64>(coll, 1.5) accepts PointN<f64>:"); let mut pad = PointN::<f64>::new(5); pad[0] = 1.5; pad[1] = -2.0; pad[2] = 0.0; pad[3] = 1.1; pad[4] = 2.2; // this assignment works only in local module // pad.items = vec![1.5, -2.0, 0.0, 1.1, 2.2]; println!("{:?} ", &pad); let vo = demo_adapters(&pad, 1.5); println!("{:?} ", &vo); println!("\nThat's all folks!\n"); }
Source Code - points_iter.rs
/*-- PointN<T> ----------------------------------- PointN<T> declares a PointN type holding a Vec<T> of coordinate values. It implements: - new(n) constructor - iter() returns iterator over items - trait IntoIterator for PointN<T> - trait IntoIterator for &PointN<T> - 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 PointN<T> where T:Debug + Default + Clone { items: Vec<T> } impl<T> PointN<T> where T:Debug + Default + Clone { pub fn new(n:usize) -> PointN<T> { PointN::<T> { items: vec![T::default(); n], } } pub fn is_empty(&self) -> bool { self.items.is_empty() } pub fn len(&self) -> usize { self.items.len() } pub fn push(&mut self, item:T) { self.items.push(item); } pub fn pop(&mut self) -> Option<T> { self.items.pop() } 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:Debug, Idx> std::ops::Index<Idx> for PointN<T> 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, Idx> std::ops::IndexMut<Idx> for PointN<T> 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<'a, T> IntoIterator for &'a PointN<T> where T:Debug + Default + Clone { type Item = T; type IntoIter = std::vec::IntoIter<Self::Item>; fn into_iter(self) -> Self::IntoIter { let ccln = self.items.clone(); ccln.into_iter() } } /*-- IntoIterator trait for PointN<T> ----------*/ impl<T> IntoIterator for PointN<T> 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() } }
Source Code - analysis_iter.rs
/*----------------------------------------------- analysis_iter.cs: Test functions with increasing functionality and increasing generality of inputs: -----------------------------------------------*/ use std::fmt::*; /*--------------------------------------------------------- Show input's call name and type - doesn't consume input - show_type is generic function with Debug bound. Using format "{:?}" requires Debug. */ pub fn show_type<T:Debug>(_t: &T, nm: &str) { let typename = std::any::type_name::<T>(); println!("{nm:?}, type: {typename:?}"); } /*--------------------------------------------------------- Show enumerable input's values - 'a is an annotation saying that T's lifetime is as long as the function's lifetime. - I is the type of T's elements, that is coll:T<I>. - T can be any iterable type and both T and I must satisfy Debug trait. - Does not consume input t since passed by reference. */ pub fn show_value_enum<T:Debug, I:Debug>( t: &T, nm: &str, left:usize, width:usize ) where for<'a> &'a T: IntoIterator<Item = &'a I> { println!("{nm:?} {{"); show_fold(t, left, width); print!("}}"); println!("\nsize: {}", std::mem::size_of::<T>()); } /*--------------------------------------------------------- Show facts about a type's elements, e.g., name, type, value, and size. - show_type is generic function with Debug bound. Using format "{:?} requires Debug." - works with small enumerable collections too because {:?} knows how to format them, but won't fold long sequences of elements. Use show_value_enum for that. */ pub fn show_type_scalar<T:Debug>(t: &T, nm: &str) { show_type(t, nm); println!( "value: {t:?}, size: {}", std::mem::size_of::<T>() ); } /*--------------------------------------------------------- Show facts about an enumerable type's elements, e.g., name, type, values, and size. - show_type is generic function with Debug bound. Using format "{:?} requires Debug." */ pub fn show_type_enum<T:Debug, I:Debug>(t: &T, nm: &str, left:usize, width:usize) where for<'a> &'a T: IntoIterator<Item = &'a I> { show_type(t, nm); show_value_enum(t, nm, left, width); } /*--------------------------------------------------------- build indent string with "left" spaces */ pub fn offset(left: usize) -> String { let mut accum = String::new(); for _i in 0..left { accum += " "; } accum } /*--------------------------------------------------------- find index of last occurance of chr in s - returns option in case chr is not found https://stackoverflow.com/questions/50101842/how-to-find-the-last-occurrence-of-a-char-in-a-string */ fn find_last_utf8(s: &str, chr: char) -> Option<usize> { s.chars().rev().position(|c| c== chr) .map(|rev_pos| s.chars().count() - rev_pos - 1) /*-- alternate implementation --*/ // if let Some(rev_pos) = // s.chars().rev().position(|c| c == chr) { // Some(s.chars().count() - rev_pos - 1) // } else { // None // } } /*--------------------------------------------------------- fold an enumerable's elements into rows of w elements - indent by left spaces - does not consume t since passed as reference - returns string https://users.rust-lang.org/t/generic-code-over-iterators/10907/3 */ pub fn fold<T, I:Debug>( t: &T, left: usize, width: usize ) -> String where for<'a> &'a T: IntoIterator<Item = &'a I>, T:Debug { let mut accum = String::new(); accum += &offset(left); for (i, item) in t.into_iter().enumerate() { accum += &format!("{item:?}, "); if ((i + 1) % width) == 0 && i != 0 { accum += "\n"; accum += &offset(left); } } let opt = find_last_utf8(&accum, ','); if let Some(index) = opt { accum.truncate(index); } accum /*-- Alternate direct implementation --*/ //let mut i = 0usize; // for item in t { // accum += &format!("{item:?}, "); // if ((i + 1) % width) == 0 && i != 0 { // accum += "\n"; // accum += &offset(left); // } // i += 1; // } } /*--------------------------------------------------------- show enumerables's elements as folded rows - width is number of elements in each row - left is indent from terminal left */ pub fn show_fold<T:Debug, I:Debug>(t:&T, left:usize, width:usize) where for<'a> &'a T: IntoIterator<Item = &'a I> { println!("{}",fold(t, left, width)); } /*--------------------------------------------------------- show string wrapped with long dotted lines above and below */ pub fn show_label(note: &str, n:usize) { let mut line = String::new(); for _i in 0..n { line.push('-'); } print!("\n{line}\n"); print!(" {note}"); print!("\n{line}\n"); } pub fn show_label_def(note:&str) { show_label(note, 50); } /*--------------------------------------------------------- show string wrapped with dotted lines above and below */ pub fn show_note(note: &str) { print!("\n-------------------------\n"); print!(" {note}"); print!("\n-------------------------\n"); } /*--------------------------------------------------------- show string wrapped in short lines */ pub fn show_op(opt: &str) { println!("--- {opt} ---"); } /*--------------------------------------------------------- print newline */ pub fn nl() { println!(); }
Output
C:\github\JimFawcett\Bits\Rust\rust_iter > cargo run -q ------------------------------ Demonstrate Rust Iteration ------------------------------ slice s = [1, 2, 3, 4, 3, 2, 1] s[2] = 3 PointN { items: [0, 1, 0, -1, 0] } using PointN<i32>.iter 0 1 0 -1 0 using PointN<i32>.into_iter --- displaying iter type --- "iter", type: "alloc::vec::into_iter::IntoIter<i32>" 0 1 0 -1 0 using PointN<i32>.into_iter iter() with auto deref 0 1 0 -1 0 using PointN<i32>.iter() 0 1 0 -1 0 using PointN<i32>.iter_mut() 0 2 0 -2 0 PointN { items: [0, 2, 0, -2, 0] } vec_indexer displays Vec<T> 1 6 2 5 3 4 slice_indexer displays slice 1 2 3 4 3 2 1 slice_indexer displays vector 5 4 3 2 1 0 slice_indexer displays string bytes 97 32 115 116 114 105 110 103 sub_range_indexer displays slice 3 4 3 slice_looper displays slice 1 2 3 4 3 2 1 slice_looper displays vector 5 4 3 2 1 0 slice_looper displays PointN 0 2 0 -3 0 collection_looper displays slice: 1, 2, 3, 4, 3, 2, 1 collection_looper displays array 1, 2, 3 collection_looper displays VecDeque 4, 3, 2, 0, -1 collection_looper displays PointN 0, 2, 0, -3, 0 PointN { items: [0, 2, 0, -3, 0] } for_looper displays slice: 1, 2, 3, 4, 3, 2, 1 for_looper displays vector: 1, 2, 3, 4, 3, 2, 1 for_looper displays VecDeque 4, 3, 2, 0, -1 for_looper displays PointN<T> 0, 2, 0, -3, 0 ranger displays string: 'a' ' ' 'l' 'i' 't' 'e' 'r' 'a' 'l' ' ' 's' 't' 'r' 'i' 'n' 'g' ranger displays values in range 0 1 2 3 4 5 6 7 8 9 ranger accepts Vector iterator 1 2 3 4 3 2 1 ranger accepts VecDeque iterator 4 3 2 0 -1 ranger accepts PointN<T> iterator 0 2 0 -3 0 demo_adapters<T, i32>(coll, 2) accepts array: [1, -1, 0, 2, 3, 4] [3, 4, 5, 6] demo_adapters<T, f64>(coll, 1.5) accepts PointN<f64>: PointN { items: [1.5, -2.0, 0.0, 1.1, 2.2] } [3.0, 2.6, 3.7] That's all folks! C:\github\JimFawcett\Bits\Rust\rust_iter >
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 >
2.0 VS Code View
3.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 |