Functional Programming in Rust
I will be using some functional programming concepts in this tutorial. Those who have never performed functional programming before may need some explanation. The strength in functional programming lies within how simple it is to perform calculations on lists of variables, regardless of their data type. A list of variables is known as a vector in Rust, abbreviated to Vec
. Using functional techniques, you can manipulate vectors with a single line of code. This is all performed without having to rely on the traditional and complicated for loop.
Iterator Type
The Iterator
type in Rust is used to perform much of the functional heavy-lifting. Any Vec
of elements can be transformed into an Iterator
using either the iter()
or into_iter()
functions. The former function, iter()
, passes values of each element by reference to eliminate the need for copying, while into_iter()
passes values by value -- copying each element.
let numbers_iterator = [0,2,3,4,5].iter();
Maps, Filters and Folds
The Iterator
type contains basic methods such as map()
, filter()
and fold()
, among many other highly useful methods. Maps apply a function to each element in a vector and return the result of each iteration to the next function. Filters
works similarly only that it will only return elements that meet a certain criteria. Folds will apply a function whose purpose is to accumulate all the elements in the vector into a single value. At the end, the collect()
function is used to return a new Vec
of values.
let sum = numbers_iterator
.fold(0, |total, next| total + next);
let squared = (1..10).iter()
.map(|&x| x * x).collect();
How Iterator Works
You may think of methods like map()
, filter()
and fold()
as specialized for loops that recurse across a vector using a series next()
calls with a function or closure as the input to apply to each iteration. Each pass through these methods returns another Iterator
type containing all of the results, so no matter how many methods you pass through, it will remain as an Iterator
until it is collected with collect()
.
Lazy Programming
Because Rust uses a lazy model of functional programming, like Haskell, it only computes what it needs. It will only perform the minimal amount of calculations needed to obtain the results required.
In the following example, because take(5)
was added after filter()
, it will stop filtering after the fifth successful filter. It will then pass those five values to map()
and each will be collected into a new Vec
by collect()
. Because of this, this is actually much more efficient than it seems at first glance to programmers that are used to programming in traditional non-lazy languages. This can actually be as fast as or even faster than a traditional for loop.
Functional Programming With Numbers
fn main() {
let vector = (1..) // Infinite range of integers
.filter(|x| x % 2 != 0) // Collect odd numbers
.take(5) // Only take five numbers
.map(|x| x * x) // Square each number
.collect::<Vec<usize>>(); // Return as a new Vec<usize>
println!("{:?}", vector); // Print result
}
Functional Programming With Strings
fn main() {
let sentence = "This is a sentence in Rust.";
let words: Vec<&str> = sentence
.split_whitespace()
.collect();
let words_containing_i: Vec<&str> = words
.into_iter()
.filter(|word| word.contains("i"))
.collect();
println!("{:?}", words_containing_i);
}
See the Rust Book
Feel free to reference the official Rust documentation for the String
, Vec
and Iterator
types. Many features aren't covered here since this program won't use them. I recommend trying out some functional programming as practice to get used to the idea. The official Rust Book is a good source of information as well.