Functional Programming
Rust không phải là một pure functional programming language như Haskell hay ML, nhưng Rust có rất nhiều concepts từ functional programming, giúp code ngắn gọn, dễ maintain và ít bugs hơn.
Các đặc điểm Functional Programming trong Rust
- Immutability by default - Variables mặc định là immutable
- First-class functions - Functions là values
- Higher-order functions - Functions nhận/trả về functions khác
- Iterators - Lazy evaluation
- Pattern matching - Declarative code
- No null -
Option<T>thay vì null - Algebraic data types - Enums với data
Immutability
#![allow(unused)]
fn main() {
// ✅ Functional style - immutable by default
let x = 5;
let y = x + 1; // Tạo giá trị mới thay vì modify
let z = y * 2;
println!("{}", z); // 12
// ❌ Imperative style - mutation
let mut x = 5;
x = x + 1;
x = x * 2;
println!("{}", x); // 12
}
Transform thay vì Mutate
#![allow(unused)]
fn main() {
// ✅ Functional - transform
fn add_one_to_all(numbers: Vec<i32>) -> Vec<i32> {
numbers.into_iter()
.map(|n| n + 1)
.collect()
}
// ❌ Imperative - mutate
fn add_one_to_all_mut(numbers: &mut Vec<i32>) {
for n in numbers.iter_mut() {
*n += 1;
}
}
}
Higher-Order Functions
Functions nhận functions khác làm arguments:
fn apply_twice<F>(f: F, x: i32) -> i32
where
F: Fn(i32) -> i32,
{
f(f(x))
}
fn add_one(x: i32) -> i32 {
x + 1
}
fn main() {
let result = apply_twice(add_one, 5);
println!("{}", result); // 7
}
Returning Functions
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |x| x + n
}
fn main() {
let add_five = make_adder(5);
println!("{}", add_five(10)); // 15
let add_ten = make_adder(10);
println!("{}", add_ten(10)); // 20
}
Iterators và Lazy Evaluation
Iterators trong Rust là lazy - chỉ compute khi cần:
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// Lazy - chưa execute
let doubled = numbers.iter()
.map(|x| {
println!("Doubling {}", x);
x * 2
});
// Chỉ execute khi collect
let result: Vec<_> = doubled.collect();
println!("{:?}", result);
}
Chaining Operations
fn main() {
let result: i32 = (1..=10)
.filter(|x| x % 2 == 0) // Chỉ số chẵn
.map(|x| x * x) // Bình phương
.sum(); // Tổng
println!("{}", result); // 220 (4 + 16 + 36 + 64 + 100)
}
Combinators
map, filter, fold
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// map: transform từng phần tử
let doubled: Vec<_> = numbers.iter()
.map(|x| x * 2)
.collect();
// filter: lọc phần tử
let evens: Vec<_> = numbers.iter()
.filter(|x| *x % 2 == 0)
.collect();
// fold: reduce về một giá trị
let sum = numbers.iter()
.fold(0, |acc, x| acc + x);
println!("Doubled: {:?}", doubled); // [2, 4, 6, 8, 10]
println!("Evens: {:?}", evens); // [2, 4]
println!("Sum: {}", sum); // 15
}
Option combinators
fn main() {
let maybe_number: Option<i32> = Some(5);
// map: transform nếu có giá trị
let doubled = maybe_number.map(|x| x * 2);
println!("{:?}", doubled); // Some(10)
// and_then: chain operations
let result = Some(5)
.and_then(|x| Some(x * 2))
.and_then(|x| if x > 5 { Some(x) } else { None });
println!("{:?}", result); // Some(10)
// filter: giữ nếu match condition
let filtered = Some(5)
.filter(|x| *x > 3);
println!("{:?}", filtered); // Some(5)
}
Result combinators
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("Division by zero".to_string())
} else {
Ok(a / b)
}
}
fn main() {
let result = divide(10.0, 2.0)
.map(|x| x * 2.0) // Transform OK value
.map_err(|e| format!("Error: {}", e)); // Transform Err value
println!("{:?}", result); // Ok(10.0)
}
Pattern Matching
Declarative control flow:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
fn process(msg: Message) -> String {
match msg {
Message::Quit => "Quitting".to_string(),
Message::Move { x, y } => format!("Moving to ({}, {})", x, y),
Message::Write(text) => format!("Writing: {}", text),
}
}
fn main() {
let msg = Message::Move { x: 10, y: 20 };
println!("{}", process(msg));
}
Recursion
// Factorial
fn factorial(n: u64) -> u64 {
match n {
0 | 1 => 1,
n => n * factorial(n - 1),
}
}
// Tail-recursive (được optimize)
fn factorial_tail(n: u64) -> u64 {
fn helper(n: u64, acc: u64) -> u64 {
match n {
0 | 1 => acc,
n => helper(n - 1, n * acc),
}
}
helper(n, 1)
}
fn main() {
println!("{}", factorial(5)); // 120
println!("{}", factorial_tail(5)); // 120
}
Functional Error Handling
Sử dụng ? operator
#![allow(unused)]
fn main() {
use std::fs::File;
use std::io::{self, Read};
fn read_file(path: &str) -> io::Result<String> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
}
Chaining với and_then
fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse()
}
fn double(n: i32) -> Result<i32, String> {
Ok(n * 2)
}
fn main() {
let result = parse_number("42")
.map_err(|e| e.to_string())
.and_then(double);
println!("{:?}", result); // Ok(84)
}
Closure Capture
fn main() {
let x = 5;
// Capture by reference
let print_x = || println!("x = {}", x);
print_x();
// Capture by move
let consume_x = move || println!("x = {}", x);
consume_x();
// x vẫn có thể dùng vì i32 impl Copy
}
Ví dụ thực tế: Data Processing
#[derive(Debug)]
struct User {
id: u32,
name: String,
age: u32,
active: bool,
}
fn main() {
let users = vec![
User { id: 1, name: "Alice".to_string(), age: 25, active: true },
User { id: 2, name: "Bob".to_string(), age: 30, active: false },
User { id: 3, name: "Charlie".to_string(), age: 35, active: true },
User { id: 4, name: "Diana".to_string(), age: 28, active: true },
];
// Functional pipeline
let active_user_names: Vec<String> = users.into_iter()
.filter(|u| u.active) // Chỉ active users
.filter(|u| u.age >= 30) // Age >= 30
.map(|u| u.name.to_uppercase()) // Uppercase names
.collect();
println!("{:?}", active_user_names); // ["CHARLIE"]
}
Functional vs Imperative
Imperative style
#![allow(unused)]
fn main() {
fn sum_of_squares_imperative(numbers: &[i32]) -> i32 {
let mut sum = 0;
for &n in numbers {
if n % 2 == 0 {
sum += n * n;
}
}
sum
}
}
Functional style
fn sum_of_squares_functional(numbers: &[i32]) -> i32 {
numbers.iter()
.filter(|n| *n % 2 == 0)
.map(|n| n * n)
.sum()
}
fn main() {
let nums = vec![1, 2, 3, 4, 5, 6];
println!("{}", sum_of_squares_imperative(&nums)); // 56
println!("{}", sum_of_squares_functional(&nums)); // 56
}
Ưu điểm của functional style:
- Dễ đọc, dễ hiểu intent
- Ít bugs (không có mutable state)
- Dễ test
- Có thể parallel dễ dàng (với
rayon)
Parallel Processing với Rayon
use rayon::prelude::*;
fn main() {
let numbers: Vec<i32> = (1..=1000).collect();
// Sequential
let sum: i32 = numbers.iter().map(|x| x * x).sum();
// Parallel - chỉ cần thêm par_iter()!
let sum_parallel: i32 = numbers.par_iter().map(|x| x * x).sum();
println!("Sum: {}", sum);
println!("Sum (parallel): {}", sum_parallel);
}
Best Practices
1. Prefer iterators over loops
#![allow(unused)]
fn main() {
// ✅ Functional
let sum: i32 = (1..=10).sum();
// ❌ Imperative
let mut sum = 0;
for i in 1..=11 {
sum += i;
}
}
2. Use combinators
#![allow(unused)]
fn main() {
// ✅ Functional
let result = Some(5)
.map(|x| x * 2)
.filter(|x| *x > 5);
// ❌ Imperative
let result = if let Some(x) = Some(5) {
let doubled = x * 2;
if doubled > 5 {
Some(doubled)
} else {
None
}
} else {
None
};
}
3. Immutability when possible
#![allow(unused)]
fn main() {
// ✅ Immutable
let numbers = vec![1, 2, 3];
let doubled: Vec<_> = numbers.iter().map(|x| x * 2).collect();
// ❌ Mutable
let mut numbers = vec![1, 2, 3];
for n in &mut numbers {
*n *= 2;
}
}
4. Chain operations
#![allow(unused)]
fn main() {
// ✅ Chaining
let result = data
.into_iter()
.filter(|x| x.is_valid())
.map(|x| x.process())
.collect();
// ❌ Intermediate variables
let filtered: Vec<_> = data.into_iter().filter(|x| x.is_valid()).collect();
let processed: Vec<_> = filtered.into_iter().map(|x| x.process()).collect();
}
Khi nào không nên dùng FP?
- Performance critical code - Imperative có thể nhanh hơn trong một số cases
- Quá nhiều allocations - Iterator chains có thể allocate nhiều
- Complex state management - Mutation có thể clear hơn
Tổng kết
Functional programming trong Rust:
- ✅ Immutability by default
- ✅ Powerful iterators
- ✅ Rich combinators (map, filter, fold…)
- ✅ Pattern matching
- ✅ Type-safe error handling
- ✅ Easy parallelization
Best practices:
- Prefer iterators và chaining
- Use combinators
- Immutability when possible
- Declarative over imperative
- Test pure functions