-- Leo's gemini proxy

-- Connecting to w1ke.cz:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

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

<- 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 - take in iterator, and return an iterator.

consumers - take an iterator, consume it (repeatedly call next), and then return a useful value


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

-- Response ended

-- Page fetched on Fri May 3 05:55:58 2024