💾 Archived View for w1ke.cz › blog › rust-iterators captured on 2024-05-26 at 14:46:24. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-01-29)

-=-=-=-=-=-=-

   /||  _  
\/\/||<(/_ 

<- index

functional rust and why iterators are the best!

2022-08-17

basics of functional programming

when i first learned rust, functional programming was alien to me. i've used go before that, which is just imperative:

// parsing lines from stdin to ints

numbers := []int{}

scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
    i, err := strconv.Atoi(scanner.Text())
    if err != nil {
    	panic(err)
    }
    numbers = append(numbers, i)
}

while in rust, you can use a more functional approach:

let numbers: Vec<i32> = std::io::stdin()
    .lines()
    .map(|line| line.parse().unwrap())
    .collect();

yes, you can also write it imperatively in rust, but this is more ergonomic, and thanks to rust's zero cost abstractions™, both of these approaches are basically equivalent in speed[1]. a core concept of functional programming in rust is iterators.

iterators

this is a simplified definition of the `Iterator` trait.

trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

that's it.

this means, that if you want to make your own iterator, it's trivially simple. but the iterator trait contains more methods, that build on top of `next`. there are multiple types of methods:

adapters

these are some cool adapters, keep in mind, that there are way more, than i can fit into this article

`.map`

the `.map` method of iterators can convert between types. it's only parameter is a closure, which takes the iterator's value as input, and returns a value. it returns an iterator, which calls the closure on each item.

`.filter`

`.filter` takes a closure, which returns a `bool`, if it's true, keep the item, if it's false, ditch it.

`.fold`

`.fold` takes an initial value of the accumulator, and a closure that takes the accumulator, and the next item. it returns the value, which will be used as the accumulator for the next iteration. this can be used for example to multiply numbers.

// let's multiply numbers from 1 to 5 (inclusive).
(1..=5)
//        ↓ initial accumulator
    .fold(1, |acc, n| acc*n);

`.take`

`.take` takes a number as argument, and takes only the specified number of elements.

// this will take numbers from 1 to 5 (inclusive).
(1..).take(5);

consumers

`.collect`

`.collect` takes an iterator, consumes it, turning it into a collection (for example a `Vec`)

let vec: Vec<_> = (1..=5).collect();

when using collect, the collection may not be type inferred, so you might need to specify the type either this way, or using turbofish syntax[2].

`.count`

`.count` takes an iterator, consumes it, and returns the number of elements in it.

laziness

in rust, iterators are "lazy", which means they do nothing until consumed, which is pretty interesting.

footnotes

1: https://doc.rust-lang.org/book/ch13-04-performance.html

2: https://turbo.fish

license

both the code, and content for this gopherhole are licensed under the Unlicense

the Unlicense

the source code