💾 Archived View for w1ke.cz › blog › rust-iterators captured on 2023-09-28 at 15:31:04. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-01-29)
-=-=-=-=-=-=-
/|| _ \/\/||<(/_
2022-08-17
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.
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:
these are some cool adapters, keep in mind, that there are way more, than i can fit into this article
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` takes a closure, which returns a `bool`, if it's true, keep the item, if it's false, ditch it.
`.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` 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);
`.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` takes an iterator, consumes it, and returns the number of elements in it.
in rust, iterators are "lazy", which means they do nothing until consumed, which is pretty interesting.
1: https://doc.rust-lang.org/book/ch13-04-performance.html
both the code, and content for this gopherhole are licensed under the Unlicense