-
R, at its heart, is a functional language - Hadley Wickham, Advanced R
Emphasizes purity: Focuses on side-effect free, “true” functions.
Avoid mutability: Promote “assignment free” programming.
Encourages declarative style: Evaluating expressions over executing statements.
Treats functions as first-class citizens: Functions are values too.
Rust has extensive support for programming in a functional style:
map
, filter
, fold
)Option
, Result
promote predictable, safe error handling.This chapter focuses on the two we have yet to cover: Closures and Iterators.
Closures : Function values that capture variables from the environment
Iterators : Abstractions for lazy processing of sequences
Core to functional programming:
map
, filter
)Example in R:
example_closure <- function(x) {
y <- 10
function() {
x + y
}
}
closure_instance <- example_closure(5)
closure_instance() # Returns 15
||
instead of ()
fn outer_function() {
let outer_variable = 10;
// Define an inner function
fn inner_function() {
// Attempt to access outer_variable
//println!("{}", outer_variable); // Error: can't capture dynamic environment in a fn item
}
// Captures `outer_variable` from the environment
let inner_closure = || {
println!("{}", outer_variable);
};
inner_function();
inner_closure();
}
fn main(){
outer_function();
}
#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor {
Red,
Blue,
}
struct Inventory {
shirts: Vec<ShirtColor>,
}
impl Inventory {
fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
user_preference.unwrap_or_else(|| self.most_stocked())
}
fn most_stocked(&self) -> ShirtColor {
let mut num_red = 0;
let mut num_blue = 0;
for color in &self.shirts {
match color {
ShirtColor::Red => num_red += 1,
ShirtColor::Blue => num_blue += 1,
}
}
if num_red > num_blue {
ShirtColor::Red
} else {
ShirtColor::Blue
}
}
}
unwrap_or_else
.|| self.most_stocked()
if user_preference
is None
variant.self
(immutable reference to Inventory)Closure can capture values in the same three ways that functions can take parameters
Immutable Reference
Mutable reference
Moving ownership
Can also explicitly move ownership:
Rust will use the highest on the list possible: Immutable reference, then mutable if needed, and then move it if needed or requested.
F
is the generic type, with trait bound FnOnce() -> T
F
can be any closure, since we only call it once.Standard library function on slices that takes a function that produces the sort key K.
Ord
is a trait for ordering, requires cmp
function, implemented by orderable types (e.g. all numbers)
Takes a FnMut instead of FnOnce because it must be called multiple times to do the sort.
sort_by_key
usage exampleFnMut
so OK.value
out.FnOnce
, not FnMut
.FnMut
(in addition to FnOnce
)iter()
on a collection by itself does nothingsum()
, collect()
, or a for
loop consume the iteratorlet v1 = vec![1, 2, 3];
// Creating an iterator (does nothing yet)
let v1_iter = v1.iter();
// Consuming the iterator in a for loop
for val in v1_iter {
println!("Got: {val}");
}
N.B. The for
syntax will call iter for you, so for val in v1
will also work.
next
.Item
is an associated type. More on this in Chapter 19.next
next
takes mutable ref to self.iter_mut
gives mutable referencesinto_iter
gives owned values.next
on an iterator.sum
and collect
filter
and map
.x
in the filter since iter
gives &&i32
v2
is not optional, collect can produce different types of collections.Rust
Haskell
clone
calls.Config::Build
didn’t own args.Config::Build
take ownership of an iterator to the args.env::args()
returns and iterator.build
Config::build
signatureargs
is that it implements an iterator that returns String
items.impl Trait
syntax was covered in Chapter 10 and is syntactic sugar for a type variable with a trait bound.Config::build
bodyimpl Config {
pub fn build(
mut args: impl Iterator<Item = String>,
) -> Result<Config, &'static str> {
args.next();
let query = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a query string"),
};
let file_path = match args.next() {
Some(arg) => arg,
None => return Err("Didn't get a file path"),
};
let ignore_case = env::var("IGNORE_CASE").is_ok();
Ok(Config {
query,
file_path,
ignore_case,
})
}
}
search
function to a functional style.results
in the original code).filter
:
results
)contains
let numbers = vec![1, 2, 3, 4, 5];
let mut result = Vec::new();
for &num in &numbers {
if num % 2 == 0 {
result.push(num * 2);
}
}
println!("Result: {:?}", result);
Iterators often compile to machine code that is as efficient as hand-written loops. This is referred to as a zero-cost abstraction
This enables concise, declarative code without sacrificing performance.
Functional programming emphasizes evaluating expressions over executing statements.
Rust provides robust support for functional programming:
map
, filter
) for transforming data.Enables concise, safe code without sacrificing performance.