Functional Programming in Rust: Idioms and Practices

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Functional Programming in Rust: From Iterators to Monads

Rust is a multi-paradigm language that seamlessly combines imperative, object-oriented, and functional styles. Despite the fact that Rust is not a purely functional language (like Haskell), its type system and ownership model allow writing elegant, safe, and expressive code in a functional style. In this article, we will break down the key concepts of functional programming (FP) in Rust: closures, iterators, pattern matching, and working with Option and Result. This material will be useful for both beginners and experienced developers looking to deepen their understanding of Rust's functional capabilities.



Closures and Lambda Expressions

Closures in Rust are anonymous functions that can capture variables from their surrounding scope. They are the foundation of the functional approach, allowing behavior to be passed as data.

The syntax for closures is concise: |parameters| body. Closures can be passed to functions, stored in variables, and returned from functions. It's important to remember the three traits that control capture: Fn (borrowing), FnMut (mutable borrowing), and FnOnce (consumption).

fn main() {    // Simple closure    let add_one = |x: i32| x + 1;    println!("{}", add_one(5)); // 6

// Capturing a variable let factor = 10; let multiply = |x| x * factor; println!("{}", multiply(3)); // 30

// Closure with FnOnce trait let name = String::from("Rust"); let greet = move || { println!("Hello, {}!", name); // name is moved }; greet(); // println!("{}", name); // Error! name is no longer available}

The move keyword forces capture by value (move), which is often necessary when working with multithreading or passing a closure to another context.



Iterators: Lazy Processing and Method Chains

One of the strongest aspects of the functional style in Rust is the powerful iterator adapter. Instead of explicit for loops and variable mutation, you use method chains: map, filter, fold, collect, and others. Iterators in Rust are lazy — they don't perform computations until a consuming method is called (e.g., collect() or for_each()).

fn main() {    let numbers = vec![1, 2, 3, 4, 5, 6];

// Functional approach: filtering and transformation let result: Vec<i32> = numbers .iter() // create an iterator .filter(|&x| x % 2 == 0) // keep only even numbers .map(|x| x * x) // square them .collect(); // collect into a vector

println!("{:?}", result); // [4, 16, 36]

// Sum using fold (reduction) let sum = numbers.iter().fold(0, |acc, x| acc + x); println!("Sum: {}", sum); // 21

// Condition checking with any and all let has_even = numbers.iter().any(|x| x % 2 == 0); println!("Has even numbers: {}", has_even); // true}

Using iterators makes code declarative, readable, and less prone to errors related to indexing and array bounds. The Rust compiler often optimizes iterator chains into efficient machine code comparable to manual loops.



Pattern Matching and Destructuring

Pattern matching (match) is the heart of functional flow control in Rust. It allows safely handling all possible variants of data, especially in combination with algebraic data types (enum). Patterns can be nested, include guards, and destructure structs, tuples, and enums.

enum Message {    Quit,    Move { x: i32, y: i32 },    Write(String),    ChangeColor(i32, i32, i32),}

fn handle_

Blogs

Book Recommendations