🚀
15. Iterators
Languages

Iterators

- Iterators are provided by the standard library

Mar 202510 min read

Iterators

  • Iterators are provided by the standard library
  • They can be implemented for any data structures
fn main() {
    let v1 = vec![1, 2, 3];
 
    let v1_iter = v1.iter();
 
    for val in v1_iter {
        println!("Got: {}", val);
    }
}
Execution Output
$ cargo run

Got: 1
Got: 2
Got: 3
  • The iter method produces an iterator over immutable references

  • All Data Structures implement the Iterator trait, which is defined in the standard library

pub trait Iterator {
    type Item;
 
    fn next(&mut self) -> Option<Self::Item>;
}
  • Item is an associated type, which means that it's a type that's associated with the Iterator trait

  • &mut self means that the method takes a mutable reference to self, because calling next changes the internal state of the iterator

#[test]
fn iterator_demonstration() {
    let v1 = vec![1, 2, 3];
 
    let mut v1_iter = v1.iter();
 
    assert_eq!(v1_iter.next(), Some(&1));
    assert_eq!(v1_iter.next(), Some(&2));
    assert_eq!(v1_iter.next(), Some(&3));
    assert_eq!(v1_iter.next(), None);
}
Execution Output
$ cargo test

running 1 test
test iterator_demonstration ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
  • The iter method produces an iterator over immutable references
  • The iter_mt method produces an iterator over mutable references
  • The into_iter method takes ownership of the data and returns an iterator that takes ownership of the values

Adapter Methods

  • Adapter methods allow you to change an iterator into a different kind of iterator
#[test]
fn iterator_increment() {
    let v1 = vec![1, 2, 3];
 
    let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
 
    assert_eq!(v2, vec![2, 3, 4]);
}
Execution Output
$ cargo test

running 3 tests
test iterator_demonstration ... ok
test iterator_increment ... ok
#[derive(PartialEq, Debug)]
struct Shoe {
    size: u32,
    style: String,
}
 
fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}

The struct derives two traits:

PartialEq : Allows two Shoe instances to be compared using == and != . Debug : Allows the struct to be formatted using the {:?} or {:#?} formatting in debug output, which is useful for printing the struct for debugging purposes.

#[test]
fn filters_by_size() {
    let shoes = vec![
        Shoe { size: 10, style: String::from("sneaker") },
        Shoe { size: 13, style: String::from("sandal") },
        Shoe { size: 10, style: String::from("boot") },
    ];
 
    let in_my_size = shoes_in_my_size(shoes, 10);
 
    assert_eq!(
        in_my_size,
        vec![
            Shoe { size: 10, style: String::from("sneaker") },
            Shoe { size: 10, style: String::from("boot") },
        ]
    );
}
Execution Output
$ cargo test

running 4 tests
test tests::filters_by_size ... ok
test tests::iterator_demonstration ... ok

Implementing the Iterator Trait

  • Implementing the Iterator trait on your own data structures allows you to use all the methods provided by the standard library
struct Counter {
    count: u32,
}
 
impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}
impl Iterator for Counter {
    type Item = u32;
 
    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;
 
        if self.count < 6 {
            Some(self.count)
        } else {
            None
        }
    }
}
#[test]
fn calling_next_directly() {
    let mut counter = Counter::new();
 
    assert_eq!(counter.next(), Some(1));
    assert_eq!(counter.next(), Some(2));
    assert_eq!(counter.next(), Some(3));
    assert_eq!(counter.next(), Some(4));
    assert_eq!(counter.next(), Some(5));
    assert_eq!(counter.next(), None);
}
Execution Output
$ cargo test

running 5 tests
test tests::calling_next_directly ... ok
test tests::filters_by_size ... ok
#[test]
fn using_other_iterator_trait_methods() {
    let sum: u32 = Counter::new().zip(Counter::new().skip(1))
        .map(|(a, b)| a * b)
        .filter(|x| x % 3 == 0)
        .sum();
    assert_eq!(18, sum);
}
Execution Output
$ cargo tests

running 6 tests
test tests::calling_next_directly ... ok
test tests::filters_by_size ... ok
  • The zip method takes two iterators and creates an iterator of pairs
  • The skip method skips the first value and returns the rest
  • The map method calls a closure on each element to change it
  • The filter method uses a closure to decide which elements to keep
  • The sum method adds up all the elements of the iterator
Calculation:

    = [1 * 2] + [2 * 3] + [3 * 4] + [4 * 5]
    = 2 + 6 + 12 + 20
    Filtering out the values that are not divisible by 3 ...
    = 6 + 12
    = 40

Removing Clone Using Iterator

pub struct Config {
    pub query: String,
    pub filename: String,
    pub case_sensitive: bool,
}
 
impl Config {
    pub fn new(args: &[String]) -> Result<Config, &str> {
        if args.len() < 3 {
            return Err("Not enough arguments");
        }
 
        let query = args[1].clone();
        let filename = args[2].clone();
        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
 
        Ok(Config { query, filename, case_sensitive })
    }
}
  • Cloning string are inefficient

  • env::args() returns an iterator that produces a series of strings, where each string is a command-line argument

  • Instead of taking ownership of a String array, we can use iterator to keep ownership of the item.

let args: env::Args = env::args();
 
let config: Config = Config::new(args).unwrap_or_else(|err| {
    eprintln!("Problem parsing arguments: {}", err);
    process::exit(1);
});
  • env::Args is an iterator that produces a series of strings, where each string is a command-line argument
impl Config {
    pub fn new(mut args: env::Args) -> Result<Config, &str> {
        if args.len() < 3 {
            return Err("Not enough arguments");
        }
 
        let query = args[1].clone();
        let filename = args[2].clone();
 
        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
 
        Ok(Config { query, filename, case_sensitive })
    }
}
  • missing lifetime specifier this function's return type contains a borrowed value

  • since we want the lifetime of the result type to exist through out the program we can use 'static lifetime specifier

impl Config {
    pub fn new(mut args: env::Args) -> Result<Config, &'static str> {
 
        args.next(); // skip the path of the program
 
        let query = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a query string"),
        };
 
        let filename = match args.next() {
            Some(arg) => arg,
            None => return Err("Didn't get a filename"),
        };
 
        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
 
        Ok(Config { query, filename, case_sensitive })
    }
}

Using Iterator Adapter

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
 
    let mut results = Vec::new();
 
    for line in contents.lines() {
        if line.contains(query) {
            results.push(line);
        }
    }
 
    results
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents.lines()
        .filter(|line| line.contains(query))
        .collect()
}
  • The lines method returns an iterator over the lines of a string
  • The filter method adapts an iterator, creating a new iterator that only contains elements that satisfy the predicate
  • The collect method consumes the iterator and collects the resulting values into a collection data type

Loops vs Iterators

  • Loops and Iterator have pretty much the same performance, because of the Zero Cost Abstraction principle ( which is using high-level abstractions without incurring runtime overhead )

© 2026 Driptanil Datta. All rights reserved.

Software Developer & Engineer

Disclaimer:The content provided on this blog is for educational and informational purposes only. While I strive for accuracy, all information is provided "as is" without any warranties of completeness, reliability, or accuracy. Any action you take upon the information found on this website is strictly at your own risk.

Copyright & IP:Certain technical content, interview questions, and datasets are curated from external educational sources to provide a centralized learning resource. Respect for original authorship is maintained; no copyright infringement is intended. All trademarks, logos, and brand names are the property of their respective owners.

System Operational

Built with Love ❤️ | Last updated: Mar 16 2026